OceanBase 0.4 自舉時庫表創建過程
傳說,在OceanBase 0.4之前,表結構定義,包括系統內部元數據表,都從Schema文件中讀取。0.4版本之後,通過SQL語句創建表結構(《OceanBase 0.4.2 SQL 參考指南》、《OceanBase 0.5 SQL 參考指南》)。
在開源的OceanBase 0.4版本(OB唯一開源版本)的RootServer代碼中,是可以看到從文件中讀取Schema配置的代碼段,在src/rootserver/ob_root_server2.cpp:340: ()
else if (!local_schema_manager_->parse_from_file(config_.schema_filename, config))
{
TBSYS_LOG(ERROR, "parse schema error chema file is %s ", config_.schema_filename.str());
res = false;
}
其中,config_.schema_filename的默認值是”etc/schema.ini“,但,這個文件安裝以後的內容很簡單(源碼構建時由src/rootserver/schema.ini拷貝至${prefix}/etc/schema.ini):
[app_name]
name=obtest # edit the app name when you deploy OceanBase for the first time
max_table_id=2000 # do not edit
說明,從0.4版本開始,至少應用的庫表Schema結構定義不再從配置文件中讀取,而是通過SQL來定義。
Schema配置文件格式
通過一個示例來大致瞭解Schema配置文件格式:
[app_name]
name=collect
max_table_id=1003
[collect_item_id]
table_id=1001
table_type=2
column_info=1,16,item_name,varchar,20
column_info=1,17,new_price,int
rowkey=item_name
rowkey_max_length=9
max_column_id=17
[collect_info]
table_id=1002
table_type=2
colume_info=1,16,uid,int
column_info=1,17,item_name,varchar,20
column_info=1,18,item_price,int
column_info=1,19,collect_time,create_time
rowkey=uid,item_name
join=[item_name$item_name]%collect_item_id:item_name$item_name,item_price$new_price
rowkey_max_length=17
max_column_id=19
應用的信息
[app_name]
name=collect
max_table_id=1003
應用的信息都寫在 app_name 這個section 中. 目前主要有兩個配置項:
- name 用來配置應用的名稱, 是一個長度不超過128位的字符串.
- max_table_id 用來記錄當前已經使用的最大的table_id.
在OceanBase中, 每個表都由table_id唯一標識, 且table_id不可以被重複使用. max_table_id 這個配置項, 主要是爲了方便 schema 生成程序記錄已經使用過的table_id.
代碼入口:src/common/ob_schema.cpp:1970
表的信息
[collect_info]
table_id=1002
table_type=2
...
rowkey=uid,item_name
join=rowkey[8,16]%collect_item_id:item_name$item_name,item_price$new_price
rowkey_max_length=17
max_column_id=19
...
每張表的信息都存放在以表名命名的section中。
table_id
配置了這張表在OceanBase系統中的唯一id,在OceanBase系統中, id的取值範圍是0-65535.系統會保留0-1000的table_id供系統自身使用.
table_type
配置表是內存表還是磁盤表,取值爲 1 的時候, 表示靜態部分放到磁盤上, 爲2的時候, 表示全部數據放到內存中.max_column_id
配置本表中已經使用過的最大的列id, 由schema 生成程序維護並使用, 防止對列id的重用.compress_func_name
可選項, 配置這個表在存儲時使用的壓縮算法名字.block_size 可選項
配置表在存儲成sstable時,採用的block大小.use_bloomfilter 可選項
配置表是否使用布隆過濾器, 非零值爲使用.rowkey_max_length
配置表中主鍵的最大長度.rowkey_split
配置表數據拆分是根據表主鍵值前多少位分佈,把數據存儲到多個tablet上存儲。這個值告訴ChunkServer, 在分拆數據到不同tablet時哪些數據是不應該被分開的, 比如, 當這個值爲9的時候, 表示主鍵前9個字節完全相同的記錄放置到同一個tablet中.
在Schema配置文件讀取程序源碼,已將屬性rowkey_max_length及rowkey_split相關代碼註解掉,但做爲表屬性還是存在的,在後面內部元數據表的屬性配置中可以看到在繼續使用
代碼入口:src/common/ob_schema.cpp:2122
列的定義
[collect_info]
...
colume_info=1,16,uid,int
column_info=1,17,item_name,varchar,20
column_info=1,18,item_price,int
column_info=1,19,collect_time,create_time
...
column_info 配置項中的內容是具體描述一列的, 用”,”分開, 其內容包含:
- 列的屬性
取值爲0或1. 0—表示該列只有動態數據(只存在於UpdateServer); 1—表示該列既有動態數據又有靜態數據(既存在於UpdateServer 又存在於 ChunkServer). - 列id
列在表中的唯一標識, 不可以被重用. 列id必須大於1, 系統保留id爲1的用於表示主鍵. - 列名
是一個長度不超過128位的字符串. - 類型
列的數據類型. - 列長度
代碼入口:src/common/ob_schema.cpp:2303
保留列ID
static const uint64_t OB_ALL_MAX_COLUMN_ID = 65535;
// internal columns id
const uint64_t OB_NOT_EXIST_COLUMN_ID = 0;
const uint64_t OB_CREATE_TIME_COLUMN_ID = 2;
const uint64_t OB_MODIFY_TIME_COLUMN_ID = 3;
const int64_t OB_END_RESERVED_COLUMN_ID_NUM = 16;
const uint64_t OB_APP_MIN_COLUMN_ID = 16;
從上面定義可看到OceanBasew保留1-15列ID內部使用,應用自定義列的ID從16開始,最大65535。ID爲2和3的列對應記錄創建時間與記錄修改時間,這兩個列ID不能用於其它列,而且這兩個列的類型值爲8(ObCreateTimeType)與9(ObModifyTimeType)這個在代碼中有所檢查,比如:src/common/ob_schema.cpp:2410:
if (parse_ok && type == ObCreateTimeType)
{
if (has_create_time_column)
{
TBSYS_LOG(ERROR, "more than one column have create time type");
parse_ok = false;
}
else
{
has_create_time_column = true;
schema.set_create_time_column(id);
}
}
if (parse_ok && type == ObModifyTimeType)
{
if (has_modify_time_column)
{
TBSYS_LOG(ERROR, "more than one column have modify time type");
parse_ok = false;
}
else
{
has_modify_time_column = true;
schema.set_modify_time_column(id);
}
}
與L2462開始處:
if (parse_ok)
{
if (!has_create_time_column && has_create_time_id)
{
TBSYS_LOG(WARN, "schema file has no create_time column, and the default create_time column id is used");
parse_ok = false;
}
if (!has_modify_time_column && has_modify_time_id)
{
TBSYS_LOG(WARN, "schema file has no modify_time column, and the default modify_time column id is used");
parse_ok = false;
}
}
RowKey
表主鍵配置項,大致格式:
column_name1(column_length%column_type),column_name2(column_length%column_type),...
其中,括號部分的內容是可選的。
從Schema定義文件中讀取RowKey定義的入口在src/common/ob_schema.cpp:2073:
index = 0;
for(vector<string>::iterator it = sections.begin();
it != sections.end() && parse_ok; ++it)
{
if (strcmp(it->c_str(), STR_SECTION_APP_NAME) == 0)
{
continue;
}
if ( !parse_rowkey_info(it->c_str(), config, table_infos_[index]) )
{
parse_ok = false;
table_nums_ = 0;
break;
}
++index;
}
方法parse_rowkey_info,從Schema定義文件中表列定義中讀取“rowkey”屬性值,其值由逗號分割的多個主鍵列組成:
bool ObSchemaManagerV2::parse_rowkey_info(const char* section_name, tbsys::CConfig& config, ObTableSchema& schema)
{
bool parse_ok = true;
//parse rowkey column
const char* rowkey_column_str = NULL;
char* clone_column_str = NULL;
ObRowkeyInfo rowkey_info;
vector<char*> column_list;
if (NULL != section_name)
{
rowkey_column_str = config.getString(section_name, STR_ROWKEY);
}
if (NULL != rowkey_column_str)
{
clone_column_str = strdup(rowkey_column_str);
tbsys::CStringUtil::split(clone_column_str, ",", column_list);
int size = static_cast<int>(column_list.size());
for (int index = 0; parse_ok && index < size; ++index)
{
ObRowkeyColumn column;
parse_ok = parse_rowkey_column(column_list[index], column, schema);
if (parse_ok)
{
schema.get_rowkey_info().add_column(column);
}
}
free(clone_column_str);
}
return parse_ok;
}
每個主鍵列完整定義形式:column_name(column_length%column_type)
, 括號當中的被稱爲compatible rowkey descriptions
(什麼鬼東西?),括號與括號的內容是可以省略的。先從表的列定義中根據colume_name查找列定義(ID、長度、類型),如果不存在compatible rowkey descriptions
就直接使用列定義相關屬性,存在剛從compatible rowkey descriptions
中解析列長度與類型,對應的方法是parse_rowkey_column:
bool ObSchemaManagerV2::parse_rowkey_column(const char* column_str, ObRowkeyColumn& column, ObTableSchema& schema)
{
// column str like
// column_name(column_length%column_type)
bool parse_ok = true;
if (NULL != column_str)
{
char* str = strdup(column_str);
str = str_trim(str);
char* s = str;
char* p = NULL;
p = strchr(s, '(');
if (NULL != p)
{
*p = '\0';
}
// no compatible rowkey descriptions, check if exist in table.
const ObColumnSchemaV2* column_schema = get_column_schema(schema.get_table_name(), s);
if (NULL == column_schema)
{
TBSYS_LOG(ERROR, "column (%s) not exist, cannot be part of rowkey.", s);
parse_ok = false;
}
else
{
column.column_id_ = column_schema->get_id();
column.length_ = column_schema->get_size();
column.type_ = column_schema->get_type();
}
if (parse_ok && NULL != p)
{
// parse compatible rowkey descriptions. (8%int)
s = p + 1;
p = strchr(s, '%');
if (NULL == p)
{
TBSYS_LOG(ERROR, "rowkey compatible format error:%s", s);
parse_ok = false;
}
if (parse_ok)
{
*p = '\0';
column.length_ = strtol(s, NULL, 10);
s = p + 1;
p = strchr(s, ')');
if (NULL == p)
{
TBSYS_LOG(ERROR, "rowkey compatible format error:%s", s);
parse_ok = false;
}
}
if (parse_ok)
{
*p = '\0';
column.type_ = ObColumnSchemaV2::convert_str_to_column_type(s);
}
}
free(str);
}
return parse_ok;
}
聯表(Join)關係的配置
join配置項的格式大概是這個樣子:[r1$jr1,r2$jr2]%joined_table_name:f1$jf1,f2$jf2,...
- joined_table_name 本表關聯的表名稱
- r1/r2/../rn 本表參與關聯的列名稱,必須爲表主鍵組成列
- jr1/jr2/…/jrn 被關聯表參與關聯的列名稱,必須爲表主鍵組成列
- f1$jf1,f2$jf2,…
參與join操作的左列和右列. join操作總是用右列的值合併到左列的值上, 然後將合併的結果返給用戶(左列和右列的值都不發生變化, 合併只體現在反給用戶的結果中).
此處,講的合併,我理解上就是以右列值替代左列的值,相關代碼入口:src/chunkserver/ob_join_operator.cpp:515
以上所有參與關聯的列,類型必須一致。
分析Schema文件中Join關係的分析代碼在這:src/common/ob_schema.cpp:3563
Schema文件分析的時序關係
RootServer啓動時,會去分析配置文件中指定路徑下的Schema定義文件。
內部元數據表的建立
在開源的OceanBase 0.4代碼的rootserver目錄下,還可以看到內部元數據表的Schema配置文件:meta_table_schema.ini,但我沒發現有地方使用這個文件。在代碼中尋找了一番,發現OceanBase內部表至少從0.4版本開始是由程序自動創建的,不再使用外部Schema配置文件。
內部表Schema讀取路徑
核心表__first_tablet_entry、 __all_all_column、__all_join_info:ObRootServer2::do_bootstrap() => ObBootstrap::bootstrap_core_tables() => ObBootstrap::create_all_core_tables() => ….. => ObSchemaServiceImpl::get_table_schema() => …… => ObExtraTablesSchema
表__all_sys_stat、__all_sys_param、__all_sys_config、__trigger_event、__users、__table_privileges、__all_cluster、__all_server、__all_client、__all_server_stat、__all_server_session、__all_sys_config_stat、__all_statement:
ObRootServer2::do_bootstrap() => ObBootstrap::bootstrap_sys_tables() => ObExtraTablesSchema
由此可見,所有內部表Schema均在類ObExtraTablesSchema中定義,採用直接填寫結構TableSchema的方式。代碼中定義表結構的片斷如下所示:
表屬性設置
table_schema.init_as_inner_table(); strcpy(table_schema.table_name_, OB_ALL_SYS_STAT_TABLE_NAME); table_schema.table_id_ = OB_ALL_SYS_STAT_TID; table_schema.rowkey_column_num_ = 2; table_schema.max_used_column_id_ = OB_APP_MIN_COLUMN_ID + 4; table_schema.max_rowkey_length_ = OB_MAX_TABLE_NAME_LENGTH + sizeof(int64_t); strcpy(table_schema.compress_func_name_, OB_DEFAULT_COMPRESS_FUNC_NAME); table_schema.rowkey_split_ = 0;
在表屬性的設置中,可以看到設置了rowkey_split屬性,但在爲核心表及Sys表建立空Tablet時並沒有使用此屬性,不知道此屬性是否起作用,何時起作用,需要在後續代碼觀看中關注。
列及列屬性設置
int column_id = OB_APP_MIN_COLUMN_ID; ADD_COLUMN_SCHEMA("cluster_id", //column_name column_id ++, //column_id 1, //rowkey_id ObIntType, //column_type sizeof(int64_t), //column length false); //is nullable ADD_COLUMN_SCHEMA("name", //column_name column_id ++, //column_id 2, //rowkey_id ObVarcharType, //column_type OB_MAX_TABLE_NAME_LENGTH, //column length false); //is nullable ADD_COLUMN_SCHEMA("data_type", //column_name column_id ++, //column_id 0, //rowkey_id ObIntType, //column_type sizeof(int64_t), //column length false); //is nullable
上面增加表列及設置屬性的語句中,column_id後面的一個參數爲非0值的,是標明此列是rowkey(主鍵)組成列,此參數也同時標明瞭在rowkey組成中的前後順序。
內部表結構的大致說明請參見《OceanBase內部表定義》
內部表自舉(BootStrap)創建流程
核心三表創建時序圖
核心表在RootServer自舉(每回看到這個詞,總感覺比較邪惡,爲毛!?)時創建一次,創建完成後,會產生成一個first_meta.bin文件,RootServer再次啓動時,如果本地存在first_meta.bin文件,則表明已經自舉過,可直接進入服務階段。
Sys表創建時序圖
Schema配置文件中數據表的建立
RootServer自舉過程中,在完成內部元數據表創建後,會依次創建Schema配置文件中定義的庫表,除不爲每個表建立空Tablet外,其餘過程基本與內部Sys表創建時序一致。
使用SQL創建表的過程
這個就先不在這裏分析了,整的有些累了,回頭再給補上。