情境
需要 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 的資料全部拿出來,然後再去重的,因為靜態列本來在底層就儲存了一份,所以不需要再去重。