导读:本期聚焦于BIT程序员创作的《如何在Node.js中使用Cassandra的SASI索引实现高效模糊查询?》,敬请观看详情。SASI是Cassandra提供的一种高效二级索引方案,支持等值查询、范围查询和LIKE模糊匹配,相比传统SAI索引之前的二级索引,它在存储和查询性能上都有明显优势。本文将围绕SASI索引的工作原理展开,讲解它的三种索引模式分别适合什么场景,分析与分析器Analyzer在中文分词中的作用,并通过Node.js的cassandra-driver演示从建表、创建SASI索引到执行模糊查询的完整流程,最后给出生产环境中的使用建议与常见坑点,帮助你在Cassandra中稳定实现类搜索引擎的查询能力。

Cassandra作为一款面向海量数据的分布式NoSQL数据库,原生的查询能力基本被主键设计所限制。如果想在非主键列上做查询,通常要靠二级索引、物化视图或者自己维护查询表。而在这些方案里,SASI(SSTable Attached Secondary Index)是一个比较特殊的存在:它把索引数据直接附着在SSTable上,支持前缀匹配、范围查询甚至分词模糊搜索,性能比传统二级索引好得多。这篇文章就来聊聊如何在Node.js项目中落地SASI索引,从原理到代码实现完整过一遍。

如何在Node.js中使用Cassandra的SASI索引实现高效模糊查询?

SASI索引是什么,它比普通二级索引强在哪

先说清楚背景。Cassandra早期提供的二级索引(Secondary Index)实现是建立在本地节点上的"伪表"机制,每次查询都要广播到所有节点,在大集群或者高基数列上性能非常差,官方基本不推荐在生产中使用。SASI从3.4版本开始引入(注意SOLR支持需要DataStax企业版,开源版的SASI在3.11可用,4.0一度移除、4.1社区又通过CEP-7回归了,使用前务必确认你的版本支持情况),它最大的改变是索引文件直接跟SSTable绑定写入,省去了额外的一层间接寻址。

SASI提供三种索引模式(MODE),这个参数直接决定了索引的行为:

  • PREFIX:按前缀组织分词,支持LIKE 'abc%'这种前缀匹配,也支持等值和范围查询,是最常用的模式。
  • CONTAINS:支持LIKE '%abc%'任意位置匹配,灵活但开销最大,数据量大了之后要谨慎使用。
  • SPARSE:针对高基数列(比如时间戳类数据)优化的模式,内存占用小,但只支持等值和范围查询,不支持LIKE。

每种模式下还可以配置分析器(ANALYZER),默认是STANDARD_ANALYZER,可以设置大小写是否敏感、是否跳过常见停用词等。如果要做文本搜索,可以考虑NON_TOKENIZING_ANALYZERTOKENIZING_ANALYZER。需要特别提醒的是,SASI内置的分析器对中文分词支持非常有限,它按空格和标点切词,中文内容会被当成一整块。如果你的业务需要中文全文检索,要么自己按需设计前缀索引策略,要么老老实实上Elasticsearch做外挂索引。

建表并创建SASI索引

下面用一个实际场景演示:用户表,需要在用户名上做前缀模糊搜索,在邮箱上做等值查询。先看CQL部分:

-- 创建自定义的SASI索引需要先定义类,通常使用内置实现
CREATE CUSTOM INDEX idx_user_name_prefix ON users (user_name)
USING 'org.apache.cassandra.index.sasi.SASIIndex'
WITH OPTIONS = {
  'mode': 'PREFIX',
  'analyzer_class': 'org.apache.cassandra.index.sasi.analyzer.StandardAnalyzer',
  'case_sensitive': 'false'
};

CREATE CUSTOM INDEX idx_user_email ON users (user_email)
USING 'org.apache.cassandra.index.sasi.SASIIndex'
WITH OPTIONS = {
  'mode': 'PREFIX',
  'case_sensitive': 'false'
};

建好索引后可以验证一下,执行一条前缀查询确认生效:

SELECT * FROM users WHERE user_name LIKE 'zhan%';

这里有几个容易被忽略的点。第一,SASI索引只能建在单一列上,不支持组合索引,如果你有多列联合查询需求,要么加冗余拼接列,要么调整数据模型。第二,mode选了CONTAINS之后写入放大比较明显,每次写入的索引开销约为原数据的一到两倍,磁盘和内存都要预留。第三,SASI查询目前不支持排序,返回结果的顺序由存储顺序决定,业务侧需要自己排序。第四,删除索引用DROP INDEX idx_user_name_prefix;即可,但大表上删索引会有一定的修复延迟。

用Node.js的cassandra-driver执行SASI查询

Node.js侧推荐DataStax官方的cassandra-driver包,安装很简单:

npm install cassandra-driver

先建立连接并插入一些测试数据:

const cassandra = require('cassandra-driver');

const client = new cassandra.Client({
  contactPoints: ['127.0.0.1:9042'],
  localDataCenter: 'datacenter1',
  keyspace: 'demo'
});

async function init() {
  await client.connect();
  console.log('已连接Cassandra');

  // 批量插入测试数据
  const insert = 'INSERT INTO users (user_id, user_name, user_email) VALUES (?, ?, ?)';
  const users = [
    [cassandra.types.Uuid.random(), 'zhangsan', 'zhangsan@ipipp.com'],
    [cassandra.types.Uuid.random(), 'zhangwei', 'zhangwei@ipipp.com'],
    [cassandra.types.Uuid.random(), 'lisi', 'lisi@ipipp.com'],
    [cassandra.types.Uuid.random(), 'zhangqiang', 'zhangqiang@ipipp.com']
  ];

  // 注意SASI索引对一致性没有特殊要求,这里用默认一致性级别
  for (const u of users) {
    await client.execute(insert, u, { prepare: true });
  }
  console.log('数据插入完成');
}

init().catch(err => {
  console.error('初始化失败:', err);
  process.exit(1);
});

接下来是核心的模糊查询部分。LIKE查询直接写在CQL里,参数绑定用?占位符即可:

async function searchByPrefix(prefix) {
  // 前缀查询,注意这里拼接百分号,且百分号作为参数值传入是安全的
  const query = "SELECT user_id, user_name, user_email FROM users WHERE user_name LIKE ?";
  const result = await client.execute(query, [prefix + '%'], { prepare: true });

  console.log(`匹配到 ${result.rows.length} 条记录:`);
  result.rows.forEach(row => {
    console.log(`${row.user_name} - ${row.user_email}`);
  });
  return result.rows;
}

// 查询所有zhang开头的用户
searchByPrefix('zhang');

如果索引模式是CONTAINS,把拼接方式换成'%' + keyword + '%'就能实现包含匹配。需要强调的是,查询中的通配符必须由应用层拼接后作为参数传入,驱动会正确处理转义,不要把用户输入直接拼进CQL字符串,否则有CQL注入风险。这一点和SQL注入的防护思路完全一致。

生产代码中还建议加上分页处理。SASI查询虽然没有原生分页语法,但可以结合fetchSize和游标实现:

async function searchWithPaging(prefix, pageSize) {
  const query = "SELECT user_id, user_name FROM users WHERE user_name LIKE ?";
  const options = { prepare: true, fetchSize: pageSize, autoPage: false };
  let result = await client.execute(query, [prefix + '%'], options);

  let page = 1;
  while (result.rows.length > 0) {
    console.log(`第 ${page} 页,${result.rows.length} 条`);
    // 处理当前页数据...

    if (!result.pageState) break; // 没有下一页了
    options.pageState = result.pageState;
    result = await client.execute(query, [prefix + '%'], options);
    page++;
  }
}

生产环境的使用建议与常见坑

第一,版本兼容性一定要提前确认。开源版Cassandra 4.0把SASI移除了,4.1才重新支持,如果你的团队用的是4.0,要么升级,要么考虑官方主推的SAI(Storage-Attached Indexing)。SAI在功能和性能上更现代,但模糊匹配能力不如SASI灵活,两者各有取舍。DataStax Astra云服务上的情况又不同,选型时先看清楚平台文档。

第二,CONTAINS模式要克制。任意位置匹配意味着索引里每个分词都要建反向映射,写入延迟和磁盘占用都会上升。经验上,如果表数据超过几亿行,CONTAINS查询的延迟会变得不稳定。更稳妥的做法是用PREFIX模式配合业务侧的查询习惯设计,比如搜索框里默认只支持首字匹配。

第三,监控索引内存。SASI会把部分索引结构放在堆外内存中,参数max_string_term_size默认限制单个词条最大24字节,超长文本建索引会报错,建索引前评估好字段长度。同时关注column_index_size相关配置,避免大value导致索引碎片化。

第四,驱动连接管理。Node.js侧建议全局复用一个Client实例,驱动内部自带连接池和心跳维护,反复创建连接会带来不必要的握手开销。查询失败时驱动有内置的重试策略,对于SASI这种可能涉及多节点协调的查询,建议显式设置读一致性级别为LOCAL_ONELOCAL_QUORUM,在性能和正确性之间取平衡。

总结一下,SASI给了Cassandra一种轻量级的模糊查询能力,尤其适合用户名搜索、前缀提示这类场景,用prefix模式加上Node.js驱动的参数绑定就能稳定工作。但如果需求演进到中文全文检索、相关性排序、高亮这些搜索引擎特性,就应该把SASI和Elasticsearch组合使用,让Cassandra专注存储、搜索引擎负责检索,各司其职才是长期可维护的架构。

CassandraSASI indexNode.js修改时间:2026-09-04 22:14:48

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260904/50513.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。