全部產品
Search
文件中心

ApsaraDB for Cassandra - Deprecated:靜態列(static column)實戰

更新時間:Jul 06, 2024

情境

需要 Cassandra 中使用一張表記錄使用者基本資料(比如 email、密碼等)以及使用者狀態更新。通常來說,使用者的基本資料一般很少會變動,但是使用者狀態會經常變化,如果每次狀態更新都把使用者基本資料都加進去,將浪費大量的儲存空間。

為瞭解決這種問題,Cassandra 引入了 static column。同一個 partition key 中被聲明為 static 的列只有一個值的,也就是只儲存一份。

定義靜態列

在表中將某個列定義為 STATIC 很簡單,只需要在列的最後面加上 STATIC 關鍵字,具體如下:

CREATE TABLE "iteblog_users_with_status_updates" (
  "username" text,
  "id" timeuuid,
  "email" text STATIC,
  "encrypted_password" blob STATIC,
  "body" text,
  PRIMARY KEY ("username", "id")
);
                        

上述命令將表中的 email 和 encrypted_password 兩個欄位設定為 STATIC。這意味著同一個 username 只會有一個 email 和 encrypted_password 。

靜態列限制

不是任何錶都支援為列加上 STATIC 關鍵字的,靜態列有以下限制:

  • 表沒有定義 Clustering columns(又稱 Clustering key),例如:

cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" (
                    ...   "username" text,
                    ...   "id" timeuuid,
                    ...   "email" text STATIC,
                    ...   "encrypted_password" blob STATIC,
                    ...   "body" text,
                    ...   PRIMARY KEY ("username")
                    ... );
InvalidRequest: Error from server: code=2200 [Invalid query] message="Static columns are only useful (and thus allowed) if the table has at least one clustering column"
                        

iteblog_users_with_status_updates_invalid 表只有 PRIMARY KEY,沒有定義 clustering column,不支援建立 Static columns。這是因為靜態列在同一個 partition key 存在多行的情況下才能達到最優情況,而且行數越多效果也好。但是如果沒有定義 clustering column,相同 PRIMARY KEY 的資料在同一個分區裡面只存在一行資料,本質上就是靜態,所以沒必要支援靜態列。

  • 建表的時候指定了 COMPACT STORAGE,例如:

cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" (
                    ...   "username" text,
                    ...   "id" timeuuid,
                    ...   "email" text STATIC,
                    ...   "encrypted_password" blob STATIC,
                    ...   "body" text,
                    ...   PRIMARY KEY ("username", "id")
                    ... )WITH COMPACT STORAGE;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Static columns are not supported in COMPACT STORAGE tables"
                        
  • 列是 partition key/Clustering columns 的一部分,例如:

cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" (
                    ...   "username" text,
                    ...   "id" timeuuid STATIC,
                    ...   "email" text STATIC,
                    ...   "encrypted_password" blob STATIC,
                    ...   "body" text,
                    ...   PRIMARY KEY ("username", "id")
                    ... );
InvalidRequest: Error from server: code=2200 [Invalid query] message="Static column id cannot be part of the PRIMARY KEY"
cqlsh:iteblog_keyspace> CREATE TABLE "iteblog_users_with_status_updates_invalid" (
                    ...   "username" text,
                    ...   "id" timeuuid,
                    ...   "email" text STATIC,
                    ...   "encrypted_password" blob STATIC,
                    ...   "body" text,
                    ...   PRIMARY KEY (("username", "id"), email)
                    ... );
InvalidRequest: Error from server: code=2200 [Invalid query] message="Static column email cannot be part of the PRIMARY KEY"
                        

為靜態列的表插入資料

含有靜態列的表插入資料和正常表類似,例如往 iteblog_users_with_status_updates 匯入資料:

cqlsh:iteblog_keyspace> INSERT INTO "iteblog_users_with_status_updates"
                    ... ("username", "id", "email", "encrypted_password", "body")
                    ... VALUES (
                    ...   'iteblog',
                    ...   NOW(),
                    ...   'iteblog_hadoop@iteblog.com',
                    ...   0x877E8C36EFA827DBD4CAFBC92DD90D76,
                    ...   'Learning Cassandra!'
                    ... );
cqlsh:iteblog_keyspace> select username, email, encrypted_password, body from iteblog_users_with_status_updates;
 username | email                      | encrypted_password                 | body
----------+----------------------------+------------------------------------+---------------------
  iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra!
(1 rows)
                        

可以看出,成功的插入一條資料了。上述語句做了兩件事:

  • 所有 username 為 iteblog 資料中的 email 和 encrypted_password 都被設定為 iteblog_hadoop@iteblog.com 和 0x877e8c36efa827dbd4cafbc92dd90d76。

  • 在 iteblog 所在的分區中新增了 body 內容為 Learning Cassandra! 的記錄。 再往表中插入一條資料,如下:

cqlsh:iteblog_keyspace> INSERT INTO "iteblog_users_with_status_updates"
                    ... ("username", "id", "body")
                    ... VALUES ('iteblog', NOW(), 'I love Cassandra!');
cqlsh:iteblog_keyspace> select username, email, encrypted_password, body from iteblog_users_with_status_updates;
 username | email                      | encrypted_password                 | body
----------+----------------------------+------------------------------------+---------------------
  iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra!
  iteblog | iteblog_hadoop@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 |   I love Cassandra!
(2 rows)
cqlsh:iteblog_keyspace>
                        

可以看出,這次插入資料的時候,並沒有指定 email 和 encrypted_password。但是從查詢結果可以看出,新增加的行 email 和 encrypted_password 的值和之前是一樣的。

現在由於某些原因,使用者修改了自己的 email,例如:

cqlsh:iteblog_keyspace> UPDATE iteblog_users_with_status_updates SET email = 'iteblog@iteblog.com'
                    ... WHERE username = 'iteblog';
cqlsh:iteblog_keyspace> select username, email, encrypted_password, body from iteblog_users_with_status_updates;
 username | email               | encrypted_password                 | body
----------+---------------------+------------------------------------+---------------------
  iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 | Learning Cassandra!
  iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76 |   I love Cassandra!
(2 rows)
                        

從上面查詢這輸出的結果可以看出, username 為 iteblog 的 email 全部修改成一樣的了,這就是靜態列的強大之處。

現在表中存在了使用者的郵箱和密碼等資訊,如果在前端的頁面支援使用者修改自己的郵箱和密碼,這時後台系統需要擷取到現有的郵箱和密碼,具體如下:

cqlsh:iteblog_keyspace> SELECT "username", "email", "encrypted_password"
                    ... FROM "iteblog_users_with_status_updates"
                    ... WHERE "username" = 'iteblog';
 username | email               | encrypted_password
----------+---------------------+------------------------------------
  iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76
  iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76
(2 rows)
                        

可以看出,表中有多少行 username 為 iteblog 的資料將會輸出多少行郵箱和密碼,這不是最終想要的資料。此時您可以在查詢的時候加上 DISTINCT 關鍵字,例如:

cqlsh:iteblog_keyspace> SELECT DISTINCT "username", "email", "encrypted_password"
                    ... FROM "iteblog_users_with_status_updates"
                    ... WHERE "username" = 'iteblog';
 username | email               | encrypted_password
----------+---------------------+------------------------------------
  iteblog | iteblog@iteblog.com | 0x877e8c36efa827dbd4cafbc92dd90d76
(1 rows)
                        

這樣無論表中有多少行 username 為 iteblog 的資料,最終都會顯示一行資料。

雖然加了 DISTINCT 關鍵字,但是 Cassandra 並不是將 username 為 iteblog 的資料全部拿出來,然後再去重的,因為靜態列本來在底層就儲存了一份,所以不需要再去重。