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