1. 概述
本文将探讨如何在 Apache Cassandra 中使用二级索引。我们将了解数据在数据库中的分布方式,并研究所有索引类型。最后,我们将讨论使用二级索引的一些最佳实践和建议。
2. Cassandra 架构
Cassandra 是一个 NoSQL 分布式数据库,采用完全去中心化的通信模型。它由多个具有同等职责的节点组成,提供高可用性。它可以在任何云提供商和本地运行,使其与云无关。我们还可以同时在多个云平台上部署单个 Cassandra 集群。它最适合 OLTP(在线事务处理)查询,在这种查询中响应速度至关重要,查询简单且很少更改。
2.1. 主键
主键是唯一标识数据记录的最重要数据建模选择。它至少由一个分区键和零个或多个聚类列组成。
- 分区键:定义数据在集群中的分布方式
- 聚类列:在磁盘上对数据进行排序,实现快速读取
看一个例子:
CREATE TABLE company (
company_name text,
employee_name text,
employee_email text,
employee_age int,
PRIMARY KEY ((company_name), employee_email)
);
这里:
company_name
作为分区键,用于将表数据均匀分布到各个节点employee_email
作为聚类列,Cassandra 使用它在每个节点上按升序保存数据,以便高效检索行
2.2. 集群拓扑
Cassandra 提供线性可扩展性,其性能与可用节点数成正比。
关键特点:
- 节点以环形排列形成数据中心
- 连接多个地理分布的数据中心创建集群
- 自动分区数据,无需手动干预,适合大数据场景
- 通过复制因子(replication factor)控制数据副本数量
数据分布示例:
如图所示:
- 表按分区键
company_name
分割并分布到各节点 - 相同
company_name
的行存储在同一物理分区 - 可通过最小 I/O 成本读取给定公司的所有数据
3. 查询非主键列
尝试查询非主键列时可能会踩坑:
SELECT * FROM company WHERE employee_age = 30;
InvalidRequest: Error from server: code=2200 [Invalid query] message="Cannot execute this query as it might involve data filtering and thus may have unpredictable performance. If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING"
关键点:
- 无法直接查询非主键列,除非使用
ALLOW FILTERING
- 但生产环境应避免使用
ALLOW FILTERING
,因为它会触发全表扫描,严重影响性能
可接受的用例:当需要对单个分区进行大量过滤时:
SELECT * FROM company WHERE company_name = 'company_a' AND employee_age = 30 ALLOW FILTERING;
这里添加了 company_name
条件,将扫描限制在单个节点上。
4. 二级索引
二级索引解决了查询非主键列的需求。在深入索引类型前,先了解 Cassandra 的写入流程:
- 数据写入仅追加的
commitlog
(写入快速) - 同时写入内存中的
Memtable
- 定期将
Memtable
刷新为不可变的SSTable
4.1. 普通二级索引 (2i)
最基本的索引类型,用于在非主键列上执行查询。
创建示例:
CREATE INDEX IF NOT EXISTS ON company (employee_age);
使用索引查询:
SELECT * FROM company WHERE employee_age = 30;
company_name | employee_email | employee_age | employee_name
--------------+-------------------+--------------+---------------
company_A | john.doe@company.com | 30 | employee_1
工作原理:
- Cassandra 创建隐藏表存储索引数据:
CREATE TABLE company_by_employee_age_idx ( employee_age int, company_name text, employee_email text, PRIMARY KEY ((employee_age), company_name, employee_email) );
- 索引数据与源数据共同位于同一节点
- 查询时从每个节点读取索引数据并收集结果
缺点:
- 集群节点多时会导致高延迟
- 读取流程:先获取匹配行的主键,再从源表获取完整数据
4.2. SSTable 附加二级索引 (SASI)
SASI 将索引生命周期绑定到 SSTable
,通过内存索引和磁盘刷新减少资源消耗。
创建示例:
CREATE CUSTOM INDEX IF NOT EXISTS company_by_employee_age ON company (employee_age) USING 'org.apache.cassandra.index.sasi.SASIIndex';
优点:
- 支持分词文本搜索
- 快速范围扫描
- 内存索引减少磁盘使用
缺点:
- 生成较大的索引文件(尤其启用文本分词时)
重要提醒:DataStax Enterprise (DSE) 中的 SASI 索引是实验性的,不推荐用于生产环境。
4.3. 存储附加索引 (SAI)
高度可扩展的索引机制,适用于 DataStax Astra 和 DataStax Enterprise。
创建示例:
CREATE CUSTOM INDEX ON company (employee_age) USING 'StorageAttachedIndex' WITH OPTIONS = {'case_sensitive': false, 'normalize': false};
normalize
选项示例:将德语字符 ö
转换为 o
,使查询 schön
时可用 schon
匹配。
优势:
- 相比 2i:吞吐量提高 43%,延迟降低 230%
- 显著减少磁盘使用
- 更简化的架构,更少的故障点
4.4. 最佳实践
使用二级索引时需注意以下要点:
添加分区键条件:将读取限制在单个节点
SELECT * FROM company WHERE employee_age = 30 AND company_name = "company_A";
限制分区键范围:通过 IN 子句限制涉及的节点数量
SELECT * FROM company WHERE employee_age = 30 AND company_name IN ("company_A", "company_B", "company_C");
限制结果数量:减少读取路径中的节点参与
SELECT * FROM company WHERE employee_age = 30 LIMIT 10;
避免低基数列:如性别、布尔列等会产生宽分区,影响性能
避免高基数列:如社保号、邮箱等会导致索引分区过细,在最坏情况下触发全节点扫描
避免频繁更新列:Cassandra 使用不可变数据结构,频繁更新会增加磁盘写入压力
5. 结论
本文探讨了 Cassandra 的数据分区机制和三种二级索引类型:
- **普通二级索引 (2i)**:基础但存在性能瓶颈
- SASI:实验性功能,适合特定场景
- SAI:性能最优的方案,推荐优先考虑
选择建议:
- 频繁访问的数据:考虑反规范化到第二个表
- 偶尔访问的数据:二级索引是更简单的选择
毫无疑问,存储附加索引 (SAI) 在三者中表现最佳,提供了卓越的性能和简化的架构。