PB表
PB表是指通过PB(Protobuf)协议定义和访问的数据表。
除PB表之外,TcaplusDB同时还提供了TDR表。
PB表与TDR表并无本质区别,用户可以根据自己对PB或TDR的熟悉程度,来决定使用PB表还是TDR表。需要注意的是当前TcaplusDB的PB表和TDR表支持的功能范围有些差异(详情见TcaplusDB是什么-2.3. 表 章节介绍),用户在选择使用体种表类型的时候,也需要考虑功能是否能满足业务需求。
1. 关于Protobuf
Protobuf是Google开发的一种描述性语言,针对结构化数据进行序列化,同时强调简单性和性能。
官方文档:https://developers.google.com/protocol-buffers
2. PB表定义文件示例
以下是protobuf表game_players.proto的示例,您可以将文件上传到控制台并创建该表。
syntax = "proto3"; // 指定protobuf语言版本,proto3.
// 导入TcaplusDB公共定义服务
import "tcaplusservice.optionv1.proto";
message game_players { // 定义TcaplusDB表,包含message类型
// 基于选择项tcaplusservice.tcaplus_primary_key创建主键字段
// TcaplusDB单个表最多能指定8个主键字段(3.37.0及其之后版本)
option(tcaplusservice.tcaplus_primary_key) = "player_id,player_name,player_email";
option(tcaplusservice.tcaplus_sharding_key) = "player_id";
// 基于选择项tcaplusservice.tcaplus_index创建主键索引
option(tcaplusservice.tcaplus_index) = "index_1(player_id, player_name)";
option(tcaplusservice.tcaplus_index) = "index_2(player_id, player_email)";
// TcaplusDB支持的数值类型:
// int32, int64, uint32, uint64, sint32, sint64, bool, fixed64, sfixed64, double, fixed32, sfixed32, float, string, bytes
// 嵌套类型: message
// 主键字段
int64 player_id = 1;
string player_name = 2;
string player_email = 3;
// 普通(非主键) 字段
int32 game_server_id = 4;
repeated string login_timestamp = 5;
repeated string logout_timestamp = 6;
bool is_online = 7;
payment pay = 8;
}
message payment {
int64 pay_id = 1;
uint64 amount = 2;
int64 method = 3;
}
3. PB表定义文件说明
3.1. TcaplusDB相关属性
TcaplusDB 表定义可以在 protobuf 语法基础上通过 option 进行扩展,可以实现更丰富的语义功能,可定义的内容如下表所示。
注:目前对于PB表,Tcaplus未限制表定义中的字段长度大小(optional uint32 tcaplus_size = 60000; // 字段大小,暂时未使用)
详细的定义格式为:option(tcaplusservice.选项) = "值";
| 选项名称 | 功能说明 | 设置示例 | 必填 |
|---|---|---|---|
| tcaplus_primary_key | 设置 TcaplusDB 表主键字段 | option(tcaplusservice.tcaplus_primary_key) = "uin,name,region"; | 是 |
| tcaplus_index | 设置 TcaplusDB 表索引键字段 | option(tcaplusservice.tcaplus_index) = "index_1(uin,region)"; | 否 |
| tcaplus_sharding_key | 用户可以自定义表分片键。默认不需要设置,当定义了有效索引(tcaplus_index)时才需要设置,而且索引必须包含分片键字段。若未定义分片键,所有本地索引的交集字段为分片键即splittablekey | option(tcaplusservice.tcaplus_sharding_key) = "uin"; | 否 |
3.1.1 tcaplus_primary_key(主键 )
tcaplus_primary_key属性指明此元素对应数据库表的主键,如果有多个成员组成表的主键,则成员名之间用逗号(',')隔开,单个表最多能指定8个主键字段。
约束:
- 作为主键的成员不能为复合数据类型,只能是基本内置数据类型。
- 作为主键的成员其取值不能为NULL。
- TcaplusDB会把所有的主键字段序列化打包到一个buffer,最大长度限制为1022字节。
option(tcaplusservice.tcaplus_primary_key) = "player_id, player_name, player_email";
根据上面的描述,game_players表使用player_id, player_name, player_email三个成员作为主键。
3.1.2. tcaplus_sharding_key(分表属性/分表因子)
tcaplus_sharding_key属性用户可以自定义表分片键。
约束:
作为此成员不能为复合数据类型,只能是基本内置数据类型。
option(tcaplusservice.tcaplus_sharding_key) = "player_id";
3.1.3. tcaplus_index(本地索引)
tcaplus_index属性用于设置TcaplusDB 表索引键字段。
option(tcaplusservice.tcaplus_index) = "player_email";
3.2. 文件定义信息
文件定义信息区域主要定义当前表描述文件的公共信息,主要涉及如下3种类型的内容:
| 选项名称 | 功能说明 | 值示例 | 必填 |
|---|---|---|---|
| syntax | 指明当前文件书写的语法规范版本。 | 支持 proto2、proto3 | 是 |
| package | 指明当前文件自定义包名,包名可以避免对 message 类型之间的名字冲突。 | 包名信息 | 否 |
| import | 引入 TcaplusDB 表的一些公共信息,必须在您的表定义中被引用。 | tcaplusservice.optionv1.proto | 是 |
3.3. 表定义信息
表定义信息主要通过 message 定义表的格式,在 message 中可对表的扩展信息进行定义,也可以对表的字段信息进行定义。
3.4. 字段定义信息
TcaplusDB 定义字段的格式为:字段修饰符 字段类型 字段名称 = 标识号[特殊定义];
PB表中必须至少定义一个value字段
3.4.1. 字段修饰符
proto2 支持3种类型的限定修饰符,proto3 不再支持 required 修饰,默认为 optional 类型。
- required:表示字段为必填字段,proto2 中主键字段必须被 required 修饰。
- optional:表示此字段为可选字段,支持设定字段的默认值。
- repeated:表示该字段可以包含0 - N个元素,其特性和 optional 一样,但是每一次可以包含多个值,可看作是在传递一个数组的值, 对于标量类型(如整型和浮点型)建议使用 [packed = true] 修饰,而非标量类型(string和message等)不能使用[packed = true],这是proto2的特性。
3.4.2. 字段类型
TcaplusDB 支持普通字段以及嵌套型字段,详细的字段介绍见下表。
proto3数据类型与编程语言类型映射
| .proto 类型 | C++ 类型 | Java 类型 | Python 类型 | Go 类型 | Ruby 类型 | C# 类型 | PHP 类型 | Dart 类型 | 描述 |
|---|---|---|---|---|---|---|---|---|---|
| double | double | double | float | float64 | Float | double | float | double | |
| float | float | float | float | float32 | Float | float | float | double | |
| int32 | int32 | int | int | int32 | Fixnum 或 Bignum (根据需要) | int | integer | int | 使用可变长度编码。 负数编码效率低下–如果您的字段可能具有负值,请改用sint32。 |
| int64 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 | 使用可变长度编码。 负数编码效率低下–如果您的字段可能具有负值,请改用sint64。 |
| uint32 | uint32 | int | int/long | uint32 | Fixnum 或 Bignum (根据需要) | uint | integer | int | 使用可变长度编码。 |
| uint64 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 | 使用可变长度编码。 |
| sint32 | int32 | int | int | int32 | Fixnum 或 Bignum (根据需要) | int | integer | int | 使用可变长度编码。 |
| sint64 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 | 使用可变长度编码。 有符号的int值。 与常规int32相比,它们更有效地编码负数。 |
| fixed32 | uint32 | int | int/long | uint32 | Fixnum 或 Bignum (根据需要) | uint | integer | int | 始终为四个字节。 如果值通常大于2^28^,则比uint32更有效。 |
| fixed64 | uint64 | long | int/long | uint64 | Bignum | ulong | integer/string | Int64 | 始终为八个字节。如果值通常大于2^56^,则比uint64更有效。 |
| sfixed32 | int32 | int | int | int32 | Fixnum 或 Bignum (根据需要) | int | integer | int | 始终为四个字节。 |
| sfixed64 | int64 | long | int/long | int64 | Bignum | long | integer/string | Int64 | 始终为八个字节。 |
| bool | bool | boolean | bool | bool | TrueClass/FalseClass | bool | boolean | bool | |
| string | string | String | str/unicode | string | String (UTF-8) | string | string | String | 字符串必须始终包含UTF-8编码或7位ASCII文本。 |
| bytes | string | ByteString | str | []byte | String (ASCII-8BIT) | ByteString | string | 可以包含不超过2^32^个任意字节序列。 |
参考链接:https://developers.google.com/protocol-buffers/docs/proto3
proto2数据类型与编程语言类型映射
| .proto 类型 | C++ 类型 | Java 类型 | Python 类型 | Go 类型 | 描述 |
|---|---|---|---|---|---|
| double | double | double | float | *float64 | |
| float | float | float | float | *float32 | |
| int32 | int32 | int | int | *int32 | 使用可变长度编码。负数编码效率低下–如果您的字段可能具有负值,请改用sint32。 |
| int64 | int64 | long | int/long | *int64 | 使用可变长度编码。负数编码效率低下–如果您的字段可能具有负值,请改用sint64。 |
| uint32 | uint32 | int | int/long | *uint32 | 使用可变长度编码。 |
| uint64 | uint64 | long | int/long | *uint64 | 使用可变长度编码。 |
| sint32 | int32 | int | int | *int32 | 使用可变长度编码。有符号的int值。与常规int32相比,它们更有效地编码负数。 |
| sint64 | int64 | long | int/long | *int64 | 使用可变长度编码。有符号的int值。与常规int64相比,它们更有效地编码负数。 |
| fixed32 | uint32 | int | int/long | *uint32 | 始终为四个字节。如果值通常大于228,则比uint32更有效。 |
| fixed64 | uint64 | long | int/long | *uint64 | 始终为八个字节。如果值通常大于256,则比uint64更有效。 |
| sfixed32 | int32 | int | int | *int32 | 始终为四个字节。 |
| sfixed64 | int64 | long | int/long | *int64 | 始终为八个字节。 |
| bool | bool | boolean | bool | *bool | |
| string | string | String | unicode (Python 2) 或 str (Python 3) | *string | 字符串必须始终包含UTF-8编码或7位ASCII文本。 |
| bytes | string | ByteString | bytes | []byte | 可以包含任意字节序列。 |
参考链接:https://developers.google.com/protocol-buffers/docs/proto
3.4.3. 字段名称
针对当前属性而进行对字段进行命名,支持大小写字母,数字与下划线。建议字段的命名采用驼峰式命名方式,不能以数字开头。
3.4.4. 标识号
标识号使用范围:[1,2^29 - 1],不可使用 [19000-19999] 标识号,因为 Protobuf 协议实现中对这些标识号进行了预留,若使用,则会报错。
每个字段在进行编码时都会占用内存,而占用内存大小取决于标识号:
- 范围 [1,15] 标识号的字段在编码时占用1个字节。
- 范围 [16,2047] 标识号的字段在编码时占用2个字节。
3.4.5. 特殊定义
当 repeated 修饰的字段需要指定 packed=true 选项。用法如下:
repeated int64 lockid = 6 [packed = true];
3.4.6. 嵌套类型信息
TcaplusDB 支持嵌套类型,嵌套类型可以包含另一个嵌套类型作为其字段,也可以在嵌套类型内定义一个新的嵌套类型。
嵌套类型的定义与表的定义相似,均是通过 message 进行声明,但是结构体中不能包含扩展信息定义,如"主键","索引"等定义。
TcaplusDB 支持最多32层连续嵌套,但是不建议大量使用嵌套,嵌套层数过多会导致数据访问性能降低。
message game_players {
option(tcaplusservice.tcaplus_primary_key) = "player_id, player_name, player_email";
int64 player_id = 1;
string player_name = 2;
string player_email = 3;
int32 game_server_id = 4;
repeated string login_timestamp = 5;
repeated string logout_timestamp = 6;
bool is_online = 7;
payment pay = 8;
}
message payment {
int64 pay_id = 1;
uint64 amount = 2;
int64 method = 3;
}
4.Generic表和List表
TcaplusDB根据表记录类型的不同,又分为Generic表和List表,Generic表一个Key(1-8个字段)记录对应一个Record记录,List表一个Key(1-7个字段)记录对应多个Record记录。List表根据是否进行排序分为普通list表和sortlist表,对于普通List表,Tcaplus内部是无序的,用户取出一个Key下的所有Value时,Value内部是无序的,但是业务可以根据插入顺序和淘汰规则来实现不同场景。SortList表按照某个指定的Value字段进行排序插入,用户可以按照从大到小或者从小到大的顺序进行取值。在PB和TDR表中都存在这三种schema模式,用户可以根据需求选择合适的模式。
4.1. 数据表及相关规则
4.1.1. 数据表规则
- 数据表名称不超过32字节(包含'\0'结尾),只能包含字母、数字、下划线,首字母为字母、下划线
- 字段名不超过32字节(包含'\0'结尾)
- 每个Key字段不超过1024字节
- 每个Value字段不超过10MB
- 单条记录Pack后长度不超过10MB
- Pb表的所有key字段,序列后的总buffer长度不超过1022字节
4.1.2. 变更规则
- 不允许对primarykey、splitkey、index做任何变更;
- Value字段可以增加字段但不能减少字段,并且增加字段时要注意版本号的变更;
- Value字段默认值可更改,Value字段最大长度只能改大不能改小;
- 允许删除Value字段,不允许更改Value字段名称和类型;
- List类型表的最大元素个数只允许改大不允许改小,且最大元素个数不能超过10000;
4.1.3. Generic表
- 可以建索引(见本地索引和全局索引)
- 最多支持8个Key字段,api版本大于等于3.46支持256个Value字段,低于3.46版本支持128个Value字段
4.1.4. List表
- 不能建本地和全局索引
- Key字段最多只能有7个,Value字段最多255个, 各有一个要预留给系统使用。
- List支持方便的头尾操作, 适合邮件,留言板,战斗记录等场景。
- List表需要指定单个Key记录下的最大元素个数(目前最大10000),超过最大元素个数时,可指定从头部或尾部删除老元素。默认采用FIFO方式淘汰。SortList表和普通List表一样有最大元素个数,插入超过最大元素个数N时,对于正序(从小排到大)总是保留最小的N个元素,倒序则反之,总是保留最大的N个元素。
- SortList表最多支持同时指定4个排序字段。
- 可以一次取出单个Key下的所有Value,Value内部的排列无序。
4.2. Generic表
PB-Gneric表示例
message pb_generic_index_shardingkey {
option(tcaplusservice.tcaplus_primary_key) = "openid,tconndid,timekey,svrid";
option(tcaplusservice.tcaplus_index) = "index_openid(openid)";
option(tcaplusservice.tcaplus_index) = "index_openid_tconndid(openid,tconndid)";
option(tcaplusservice.tcaplus_index) = "index_full_key(openid,tconndid,timekey,svrid)";
option(tcaplusservice.tcaplus_sharding_key) = "openid";
//4个key
required uint32 openid = 1; //QQ Uin
required string timekey = 2[ (tcaplusservice.tcaplus_desc) = "bingo"];
required int32 tconndid = 3;
required string svrid = 4;
//value
required string gamesvrid = 5;
repeated property other_property= 6 ;//其他扩展属性
optional item_bag items = 7;
repeated int64 lockid = 8 [packed = true];
optional bytes val = 9;
optional pay_info pay = 10;
optional uint32 id_uint32 = 11;
optional int32 id_int32 = 12;
}
4.3. List表
TcaplusDB对List表的存储采用分级机制,包括:
- 索引记录,FullKey + (idx1,idx2,idx3,……,idxn)
- 数据记录,[FullKey+idx1,value1][FullKey+idx2,value2][……][FullKey+idxn,valuen]
因list表的实现机制上存在系统索引记录(每个key都会存在一条索引记录),所以业务实际记录数会小于TotalSize总记录数(key个数+实际记录数)。
例如:遍历查询表里实际记录41条,其中key个数26个,那么count表的TotalSize总记录数为67
4.3.1. list表index分配机制
- index从0开始,index的值是后台自动生成的,用户不能自定义index的值
- index按照步长1从0开始自增,直到自增到int类型的最大值,然后会选择一个空闲的index使用,即不管什么情况下,正在使用的index不会重复
- 如果key删除,index从0开始,也可以保持从上次index开始 默认从0开始,也可以通过SetFlags设置从上次index开始
/*TCAPLUS_FLAG_LIST_RESERVE_INDEX_HAVING_NO_ELEMENTS
* 设置此标志后,List表删除最后一个元素时需要保留index和version。
* ListDelete ListDeleteBatch ListDeleteAll操作在删除list表最后一个元素时,
* 设置此标志在写入新的List记录时,版本号依次增长,不会被重置为1。
*
* 适用场景:
* 业务需要确定某个表在删除最后一个元素时是否需要保留index和version
* 主要涉及List表的使用体验
*
*/
int SetFlags(int32_t flag);
index是不连续的,只会增加比如:1 2 3 4 5 删除了3变成了 1 2 4 5 ,如果AddRecord(2)就变成了 1 2 6 4 5
- 如果key下的 list元素都被删除,等同于key也被删除
- listaddafter支持队头删除,队尾插入;或者队头插入,队尾删除
TCAPLUS_API_LIST_PRE_FIRST_INDEX:新元素插入在第一个元素之前
TCAPLUS_API_LIST_LAST_INDEX:新元素插入在最后一个元素之后
4.3.2. list表空索引保留设置
list表空索引,是指list表某个主键下面存在索引记录,但是不存在元素记录的情况,比如业务调用了ListDeleteAll请求并且设置了需要保留索引记录,那么存储层就会把这个主键下面的元素记录全部删除,并保留索引记录,保留的这个索引记录就是所说的空索引;
其中空索引的值即index默认从0开始,也可以通过SetFlags设置从上次index开始,具体详见sdk中SetFlags方法说明
4.3.3. list表淘汰策略
/**
@brief 设置LIST满时,插入元素时,删除旧元素的模式--TDR接口才能直接调用此方法
@param [in] chListShiftFlag TCAPLUS_LIST_SHIFT_NONE: 不允许删除元素,若LIST满,插入失败;TCAPLUS_LIST_SHIFT_HEAD: 移除最前面的元素;TCAPLUS_LIST_SHIFT_TAIL: 移除最后面的元素
如果表是排序List,必须要进行淘汰,且淘汰规则是根据字段的排序顺序进行自动制定的,用户调用该接口会失败
@retval 0 设置成功
@retval 非0 设置失败,具体错误参见 \link ErrorCode \endlink
*/
int32_t SetListShiftFlag(IN const char chListShiftFlag = TCAPLUS_API_LIST_SHIFT_HEAD);
当设置为淘汰策略为TCAPLUS_LIST_SHIFT_HEAD(列表满了之后,淘汰列表头部的元素)时,配合尾部插入一起使用,便可实现类似先进先出的队列。
当设置为淘汰策略为TCAPLUS_LIST_SHIFT_TAIL(列表满了之后,淘汰列表尾部的元素)时,配合头部插入一起使用,也可实现类似先进先出的队列。
4.3.4. PB-List表示例
syntax = "proto3";
package myTcaplusTable;
import "tcaplusservice.optionv1.proto";
message tb_online_list {
option(tcaplusservice.tcaplus_primary_key) = "openid,tconndid,timekey";
option(tcaplusservice.tcaplus_customattr) = "TableType=LIST;ListNum=1900";
int32 openid = 1; //QQ Uin
int32 tconndid = 2;
string timekey = 3;
string gamesvrid = 4;
int32 logintime = 5 ;
repeated int64 lockid = 6;
pay_info pay = 7;
message pay_info {
uint64 total_money = 1;
uint64 pay_times = 2;
}
map<string, pay_info> projects = 8;
}
4.4. SortList表
最多允许4个排序字段(一级字段),且在建表的proto中指定,具体格式如下:
message pb_sortedlist {
option(tcaplusservice.tcaplus_primary_key) = "openid,tconndid,timekey,svrid";
option(tcaplusservice.tcaplus_customattr) = "TableType=SORTLIST;ListNum=1023;SortField=id_int32;SortRule=DESC[ASC]";//多字段排序SortField=id_int32,id_uint32..
//4个key
required uint32 openid = 1; //QQ Uin
required string timekey = 2[(tcaplusservice.tcaplus_desc) = "bingo"];
required int32 tconndid = 3;
required string svrid = 4;
//value
required string gamesvrid = 5;
repeated property other_property= 6 ;//其他扩展属性
optional item_bag items = 7;
repeated int64 lockid = 8 [packed = true];
optional bytes val = 9;
optional pay_info pay = 10;
optional uint32 id_uint32 = 11;
optional int32 id_int32 = 12;
}
如上option(tcaplusservice.tcaplus_customattr) = "TableType=SORTLIST;ListNum=1023;SortField=id_int32";表示该表格类型为SORTLIST,ListNum=1023表示单个Key记录下的最大元素个数是1023, SortRule=DESC表示降序排列,不支持多个字段指定排序规则单独排序。
- 排序默认按照从小到大进行排序
- 暂时不允许在表变更时从无序List变为SortList,也不允许从SortList变更为无序List,不允许表变更变换排序字段的顺序以及增减排序字段(这个均采用自己写so走表变更Key-Value方式实现)。
- 排序字段最大字节数8B,排序字段的类型:byte, uint16,uint32,uint64,int16,int32,int64,float,double【string(包含\0最多8B,暂时不支持)】。
- 排序是指Value字段参与排序,而不是Key。
使用说明:
排序
当已有数据结构排好序后,再采用ListAddAfter进行数据插入时,采用插入排序效果最佳。 允许多个排序字段联合排序(最多4个),先按照第一个字段进行排序,第一个字段相同时,再按照第二个字段排序,依次类推排序。
插入
对于ListAddAfter,流程是:先看是否已经满了,如果满了且不允许淘汰元素,则插入失败,如果满了,允许淘汰,则删掉那个元素,并且获取一个BiggestIndex,将新元素插在对应位置,并挪动其他元素。
而对于SortList,当用户进行ListAddAfter时,List数目超过最大元素个数N时,对于正序(从小排到大)总是保留最小的N个元素,也就是说插入的元素比当前最大值还大时,会插入失败(因为立即被淘汰)返回错误码 SVR_ERR_FULL_SORTLIST_CANT_INSERT,而倒序则反之,总是保留最大的N个元素。(即SetListShiftFlag中的淘汰规则对sortlist表失效,按照排序规则淘汰)
PB-SortList表示例
message pb_sortedlist {
option(tcaplusservice.tcaplus_primary_key) = "openid,tconndid,timekey,svrid";
option(tcaplusservice.tcaplus_customattr) = "TableType=SORTLIST;ListNum=1023;SortField=id_int32";//多字段排序SortField=id_int32,id_uint32..
//4个key
required uint32 openid = 1; //QQ Uin
required string timekey = 2[(tcaplusservice.tcaplus_size) = 20, (tcaplusservice.tcaplus_desc) = "bingo"];
required int32 tconndid = 3;
required string svrid = 4;
//value
required string gamesvrid = 5[(tcaplusservice.tcaplus_size) = 11];
repeated property other_property= 6 ;//其他扩展属性
optional item_bag items = 7;
repeated int64 lockid = 8 [packed = true];
optional bytes val = 9;
optional pay_info pay = 10;
optional uint32 id_uint32 = 11;
optional int32 id_int32 = 12;
}
5. PB表修改限制
- 主键字段不能删除。
- 主键字段名和字段类型不能改变。
- 不能增加主键字段。
- 对于非主键字段,required字段不可以删除,但optional字段可以删除。
- 同标识号的字段名称和字段类型不能改变。字段的标识号不可变更。也就是说字段的标识号+名称+类型一旦定义不可修改,只能删除。
- 增加的普通字段名要符合命名规则, 不能修改原有字段类型、名称及字段标号。
- 本地索引不能新增,删除和修改,必须在建表就定义好。
5.1. 示例1:增加字段

syntax = "proto3";
import "tcaplusservice.optionv1.proto";
message game_players {
option(tcaplusservice.tcaplus_primary_key) = "player_id, player_name, player_email";
option(tcaplusservice.tcaplus_index) = "index_1(player_id, player_name)";
option(tcaplusservice.tcaplus_index) = "index_2(player_id, player_email)";
int64 player_id = 1;
string player_name = 2;
string player_email = 3;
int32 game_server_id = 4;
repeated string login_timestamp = 5;
repeated string logout_timestamp = 6;
bool is_online = 7;
payment pay = 8;
string player_email_copy = 9; // 增加value字段
}
message payment {
int64 pay_id = 1;
uint64 amount = 2;
int64 method = 3;
}