本节摘要:CQL 表面像 SQL,内核强制分区键完整性——无
JOIN、无无分区键WHERE(除非 ALLOW FILTERING)。CREATE KEYSPACE定义复制宪法;2011 年 1.2 起 CQL 取代 Thrift 成为默认接口(SOURCE 3.1)。
阅读完本节,你应当能够:
CREATE TABLEINSERT/UPDATE/DELETE 与 USING TTLALLOW FILTERING 为何是性能告警开发者看到 SELECT 就以为能 WHERE email = ?——在 Cassandra 里,若 email 不是 partition key 的一部分,Coordinator 必须扫描全环,延迟从毫秒变分钟。MongoDB 可在任意字段建索引(代价是写放大);PostgreSQL B-tree 索引成熟。CQL 的设计是:在语法层拦住分布式灾难,把责任前移到建模。
Keyspace = 复制 + 一致性策略容器:
CREATE KEYSPACE retail WITH replication = { 'class': 'NetworkTopologyStrategy', 'dc1': 3 } AND durable_writes = true;
表 = partition 内有序宽行:
CREATE TABLE sensor_readings ( device_id text, reading_time timestamp, temperature double, PRIMARY KEY (device_id, reading_time) ) WITH CLUSTERING ORDER BY (reading_time DESC);
语义:同一 device_id 共置;reading_time 仅分区内排序。合法查询:WHERE device_id = ? AND reading_time > ?。非法:WHERE temperature > 30(无 partition key)。
| CQL 特性 | 说明 | Mongo 对照 |
|---|---|---|
| PRIMARY KEY | 含 partition + clustering | _id 默认单键 |
| TTL | 列/行自动过期 | TTL index |
| LWT | IF NOT EXISTS |
findAndModify |
| UDT/JSON | 5.x 嵌套类型 | 原生 BSON |
常用 DML:
INSERT INTO sensor_readings (device_id, reading_time, temperature) VALUES ('dev-9', toTimestamp(now()), 22.5) USING TTL 86400; UPDATE sensor_readings USING TIMESTAMP 1700000000000 SET temperature = 23.0 WHERE device_id = 'dev-9' AND reading_time = '2024-01-01';
类型:用 uuid、timeuuid 作 clustering 可时间排序;避免 text 超大 blob——改对象存储 + Cassandra 存指针。
索引:SAI(4.x)优于 legacy 2i 的写放大;仍不如把查询字段放进 PRIMARY KEY 可靠。
cqlsh:CONSISTENCY LOCAL_QUORUM 会话级设置;生产用 DataStax Java/Python 驱动 4.x。
⚠️ 常见坑:
SELECT *在大 wide row 上拉取百万列——应限 clustering 范围。
💡 关键直觉:CQL 每条合法语句都隐含「只碰少数 partition」——违反即反模式。
下一节深入 PRIMARY KEY 如何映射 Token Ring 与热点治理。
从零建一个「订单 + 明细」的宽列模型,体会 CQL 的完整拼图:
CREATE KEYSPACE shop WITH replication = { 'class': 'NetworkTopologyStrategy', 'dc-east': 3 }; CREATE TABLE orders_by_user ( user_id text, order_ts timestamp, order_id text, total_amount decimal, status text, PRIMARY KEY (user_id, order_ts, order_id) ) WITH CLUSTERING ORDER BY (order_ts DESC); -- 插入:PRIMARY KEY 的每个字段都必须给全 INSERT INTO orders_by_user (user_id, order_ts, order_id, total_amount, status) VALUES ('u-7788', '2024-08-14 10:00:00', 'ord-1', 199.00, 'paid'); -- 合法的范围查询:分区键等值 + 聚类键范围 SELECT * FROM orders_by_user WHERE user_id = 'u-7788' AND order_ts >= '2024-08-01'; -- 非法的查询:必须带分区键 -- SELECT * FROM orders_by_user WHERE status = 'paid';
-- UPDATE 语义:按主键定位,无则插入(upsert) UPDATE orders_by_user SET status = 'shipped' WHERE user_id = 'u-7788' AND order_ts = '2024-08-14 10:00:00' AND order_id = 'ord-1'; -- DELETE 写 tombstone,物理清理交给 Compaction DELETE FROM orders_by_user WHERE user_id = 'u-7788' AND order_ts = '2024-08-14 10:00:00' AND order_id = 'ord-1';
| 类型 | 用途 | 注意事项 |
|---|---|---|
| uuid / timeuuid | 无序列号主键 | timeuuid 可排序,uuid 不可 |
| counter | 原子计数器 | 不能建在普通表主键上,需单独 counter 表 |
| map / set / list | 集合列 | 集合修改会整列重写,勿高频更新 |
| frozen | 嵌套结构快照 | 冻结后只能整体替换 |
| blob / text | 二进制与大文本 | 超大 blob 应存对象存储 |
CREATE TABLE user_tags ( user_id text PRIMARY KEY, tags set<text> ); -- 集合操作:整列重写是 CQL 的固有语义 UPDATE user_tags SET tags = tags + {'vip'} WHERE user_id = 'u-1';
# 非交互查询:适合脚本与 CI cqlsh -e "SELECT * FROM shop.orders_by_user LIMIT 10" # 导出结果集到文件 cqlsh -e "COPY shop.orders_by_user TO '/tmp/orders.csv'" # 追踪单条语句的分布式路径 cqlsh -e "TRACING ON; SELECT * FROM shop.orders_by_user LIMIT 1"
COPY 命令支持导入导出,是小数据量迁移与备份的常用工具;大数据量仍建议 sstableloader 或驱动流式写入。TRACING 是诊断「这条查询扫了几个分区」的首选工具——看到 rows 与 sstables 数量就能判断查询是否命中建模预期。
| 差异 | SQL(PostgreSQL) | CQL |
|---|---|---|
| 查询入口 | 任意列可过滤 | 必须含完整分区键 |
| 数据组织 | 规范化 + JOIN | 反规范化 + 双表 |
| 更新语义 | UPDATE 覆盖旧行 | 按列时间戳仲裁(upsert) |
-- 最后一条示例:CQL 的 upsert 语义(同主键再写即更新) INSERT INTO orders_by_user (user_id, order_ts, order_id, status) VALUES ('u-7788', '2024-08-14 10:00:00', 'ord-1', 'cancelled'); -- 若其他列(amount)曾写入且时间戳更新,将保留;本语句未写 amount 不会置空
这条特性在生产中常被忽视:CQL 的 UPDATE/INSERT 是「列级时间戳仲裁」,不是行级整体替换。所以补丁式写入不能靠「只传部分列」清空旧值——清空必须显式 DELETE 该列。理解这一点,才算真正告别 SQL 心智进入 CQL 世界。