【TcaplusDB知识库】条件过滤说明与更新
1. 介绍
针对更灵活的数据访问操作,TcaplusDB 支持条件操作的能力,具备以下能力:
-
条件查询: 对单条记录或批量记录查询时,可指定记录级别的过滤条件,要求返回记录满足条件。
-
条件更新: 对单条记录或批量记录修改或删除时,可指定记录级别的过滤条件,只有条件满足时才会执行变更。
-
数组更新: 数组类型的字段(如 protobuf 中的 repeated 字段),支持对数组中元素进行增删改操作,可以只处理满足某些条件过滤的元素。
-
数组查询: 数组类型的字段(如 protobuf 中的 repeated 字段),查询数组中返回满足过滤条件的或者某些下标范围内的数据,而不是完整的记录。
generic 表和 list 表都支持条件操作。
2. 示例表定义
这里分别定义两种类型的表,用于本章节示例,为了更直观说明条件过滤和更新。本章节以protobuf协议作为示例,但TDR协议同样支持条件过滤和更新,使用类似。
-
Generic 表
syntax = "proto3";package myTcaplusTable;import "tcaplusservice.optionv1.proto";message user { option(tcaplusservice.tcaplus_primary_key) = "id,name"; option(tcaplusservice.tcaplus_index) = "name_index(name)"; message mail { string title = 1; string content = 2; } int32 id = 1; string name = 2; int32 rank = 3; repeated int64 gameids = 4; repeated mail mailbox = 5;}
-
List 表
syntax = "proto3";package myTcaplusTable;import "tcaplusservice.optionv1.proto";message list_user { option(tcaplusservice.tcaplus_primary_key) = "id,name"; option(tcaplusservice.tcaplus_customattr) = "TableType=SORTLIST;ListNum=1024;SortField=rank"; message mail { string title = 1; string content = 2; } int32 id = 1; string name = 2; int32 rank = 3; repeated int64 gameids = 4; repeated mail mailbox = 5;}
3. 条件过滤说明
支持记录级别的条件过滤,只有满足条件,才对指定的(一个或多个)记录进行操作,包括对记录修改、删除或查询等。
3.1 解决什么问题
若没有条件过滤,对于 generic 表,通过主键查询或操作一个记录,若对应主键不存在,则返回错误码 TXHDB_ERR_RECORD_NOT_EXIST。
而条件则在这基础上再加一层过滤,对于 generic 表,主键 key 对应存在基础上,必须条件满足才能查询或操作对应的记录,否则返回错误码 COMMON_ERR_CONDITION_NOT_MATCHED。
key + condition,相当于 SQL 中的 where 语句,虽然能力比 SQL 还差不少,但已经提供一定程度的灵活能力。 特别是对于“读判断-然后写”的原子操作场景特别有用,条件更新的初衷就是解决这个问题的。
例如,对于 user 表,若 gameids 数组不包含 101 这个元素,那么在该数组插入 101,那么应用代码会写成如下
user u;// 设置主键u.set_id(1);u.set_name("a");int ret = api.Get(&u);// ...if (!Find(u.gameids(), 101)) // Find是应用实现的函数,在数组中找是否存在某元素{ u.add_gameids(101); ret = api.Set(&u); // ...}
复制代码
上述代码存在几个问题
-
应用端和服务端存在多处交互,先 Get 再 Set。
-
若 user 结构较大,那么这个流程中涉及的序列化、反序列带来不必要的开销较大。
-
最严重的是,Get + Set 两次交互,这个逻辑不是原子的,若应用端存在多个这样流程并发,可能重复插入 101,这造成逻辑错误。由于 TcaplusDB 还不支持事务,应用解决该问题比较繁琐。
针对上述问题,条件更新可以解决一些交互多、非原子等问题,示例条件操作代码如下:
user u;// 设置主键u.set_id(1);u.set_name("a");// 先判断gameids是否已经包含101,若不存在(条件不满足)再插入101int ret = api.UpdateItem(&u, "PUSH gameids#[-1][$ = 101]", "gameids NOT CONTAINS($==101)");if (ret == COMMON_ERR_CONDITION_NOT_MATCHED) // 条件不满足,说明gameids已经存在101了{ // ...}
复制代码
上述代码,"gameids NOT CONTAINS($==101)"就是过滤条件,而"PUSH gameids#[-1][$ = 101]"是数组操作语句,后文介绍,这里指在 gameids 数组尾部插入 101。
3.2 条件过滤接口说明
TcaplusDB 提供的一些 protobuf API 新增了 const std::string &condition 入参,用于指定过滤条件,支持 condition 的接口以及新增的错误码详见后文的附录。
一些接口的使用示例如下,更多的示例可见详细 example。
// tcaplus_coroutine_pb_api.hint Set(::google::protobuf::Message *msg, const std::string &operation = "", const std::string &condition = "");int FieldInc(const std::set<std::string> &dottedpaths, ::google::protobuf::Message *msg, const std::string &operation = "", const std::string &condition = "");int Traverse(::google::protobuf::Message *msg, const std::string &condition, TcaplusTraverseCallback *cb);// example.cppuser u;// 设置主键u.set_id(1);u.set_name("a");// 设置其他内容// ...// 当rank>100,才执行Set操作,若条件不满足会返回对应的错误码int ret = api.Set(&u, "", "rank > 100");if (ret == COMMON_ERR_CONDITION_NOT_MATCHED) { ... } // 条件不满足的情况// 设置递增的步长u.set_rank(1);// 当rank达到上限100之后,不在对rank递增,否则 +1std::set<std::string> dottedpaths;dottedpaths.insert("rank");ret = api.FieldInc(dottedpaths, &u, "", "rank < 100");// 遍历2021-01-01年之后修改过的记录,这里$.LastAccessTime是记录的内建属性,表示记录的最后更新时间int ret = api.Traverse(&u, "$.LastAccessTime >= \"2021-01-01\"", &traverseCallback);
复制代码
3.3 条件过滤语法说明
过滤条件是类 SQL 的 where 语句的语法,已支持以下几种过滤能力
-
比较,如
rank > 1,比较符有>, >=, <, <=, =, ==, !=,在比较的上下文中,=也是相等比较,而在其他语境下可能是赋值符号 。 -
逻辑运算,如
rank > 1 AND rank < 10,运算符有AND, OR, NOT。 -
位运算,仅支持“与”,如
filter & 8,当从低往高的第三位为 1 时,该表达式为 true。 -
CONTAINS和NOT CONTAINS,即判断是否包含,如"mailbox CONTAINS(title == \"tcaplus\")"表示要求 mailbox 包含一个 title 等于"tcaplus"的元素。CONTAINS 括号中可以是更复杂的子条件。 -
内建属性
$.LastAccessTime,该内建属性表示记录的最后更新时间,最小精度为秒,可用于和字符串表示的时间进行比较,如"2021"、"2021-01-01"或"2021-01-01 00:00:00"。 -
当前数组元素的引用
$,如,"gameids NOT CONTAINS($==101)"。
完整语法如下
condition_expr ::= array CONTAINS '(' condition ')' | array NOT CONTAINS '(' condition ')' | conditioncondition ::= operand | operand comparator operand | condition AND condition | condition OR condition | NOT condition | operand bitwise_op operandcomparator ::= == | = | < | > | <= | >= | !=bitwise_op ::= &array ::= identifieroperand ::= identifier | number | string | $ | $.LastAccessTime
复制代码
-
语法说明
-
identifier: 一个合法的标识名称,在这里是字段名或字段名路径,如
name或mail.title。 -
number: 整型或浮点数,不支持大整数。
-
string: 双引号或者单引号括起来的字符串。
-
-
比较
-
不同精度的整型或浮点型的数值都是可以相互比较的,这和 C++语言中是一致的,例如 int16 和 int32 比较,前者的类型会被提升之后再比较,整型和浮点比较,整型则会先被提升为浮点型。
-
int 和 uint 可比较,会先比较符号位。
-
浮点型的等值会有精度偏差。
-
字符串也是可比较,按照字母字典序,这和 C++中的 std::string 的比较行为是一致的。
-
数值类型和字符串直接不可比较。
-
-
操作符优先级
-
条件表达式 condition 中,操作符优先级,从高到低为
comparator NOT AND OR,例如"a==1 OR a>10 AND a<20",会先计算 AND 的结果再计算 OR。 -
当然可以使用括号来分隔条件表达式,例如
"(a==1 OR a>10) AND a<20"则就先计算 OR。
-
3.4 性能优化建议
条件过滤的性能和 1)条件表达式、2)表的模式 有关,满足以下规则时,有针对性的性能优化(仅供参考,内部实现可能会调整):
-
当条件表达式只用到 key 字段(包括主键和 index 字段)和记录的属性字段(如$.LastAccessTime),仅通过记录的主键或索引即可进行判断,无需从存储引擎读取全量数据。
-
对于 SortList 表,当条件表达式只使用了 sort 字段,也有性能优化。
-
对于 SortList 表,表定义的排序字段只有一个,且条件表达式是简单的二元比较(如
"field >= 1"和"field == 1"等),有更好的性能优化,即使用二分查找。
TcaplusDB是腾讯出品的分布式NoSQL数据库,存储和调度的代码完全自研。具备缓存+落地融合架构、PB级存储、毫秒级时延、无损水平扩展和复杂数据结构等特性。同时具备丰富的生态、便捷的迁移、极低的运维成本和五个九高可用等特点。客户覆盖游戏、互联网、政务、金融、制造和物联网等领域。




近期评论