【TcaplusDB知識庫】條件過濾說明與更新

zmgogo發表於2021-11-23


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,若不存在(條件不滿足)再插入101
int 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.h

int 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.cpp
user 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遞增,否則 +1
std::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 ')'
    | condition

condition :: =
      operand
    | operand comparator operand
    | condition AND condition
    | condition OR condition
    | NOT condition
    | operand bitwise_op operand

comparator :: =
      ==
    | =
    | <
    | >
    | <=
    | >=
    | !=

bitwise_op :: =
    &

array :: =
    identifier

operand :: =
      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" 等),有更好的效能優化,即使用二分查詢。


img

TcaplusDB是騰訊出品的分散式NoSQL資料庫,儲存和排程的程式碼完全自研。具備快取+落地融合架構、PB級儲存、毫秒級時延、無損水平擴充套件和複雜資料結構等特性。同時具備豐富的生態、便捷的遷移、極低的運維成本和五個九高可用等特點。客戶覆蓋遊戲、網際網路、政務、金融、製造和物聯網等領域。



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69982264/viewspace-2843733/,如需轉載,請註明出處,否則將追究法律責任。

相關文章