导读:本文基于 Hazelcast 官方设计文档 docs/design/sql/12-create-index.md,系统讲解 Hazelcast SQL 引擎中
CREATE INDEX语句的设计与实现。该能力自 Hazelcast 5.1 引入,用于通过 SQL 为 IMap 动态创建索引,是 SQL 引擎动态配置能力的重要补充。读完本文,你将掌握CREATE INDEX的完整语法、三种索引类型(SORTED / HASH / BITMAP)及 BITMAP 专属选项的用法,理解索引名称作用域、与 SQL 映射(Mapping)的关系、权限模型等设计约束,并能结合源码理解其从 SQL 解析到IMap#addIndex调用的完整链路。
一、背景与动机
IMap(分布式 Map)是 Hazelcast 最核心的数据结构,其查询性能高度依赖索引。在 Hazelcast 5.1 之前,索引只能通过编程式配置(IndexConfig)或 XML/YAML 配置在成员端定义;Hazelcast SQL 引擎的推出使得用户希望通过 SQL 直接管理索引,从而提升 SQL 引擎的动态配置能力、丰富 SQL 语法。
本设计文档正是为了实现"通过 SQL 为 IMap 创建索引"这一目标而编写,对应的底层实现是IMap#addIndex(IndexConfig)方法的调用。
需要特别说明的是:当前版本只支持索引的创建,尚不支持索引删除。虽然文档中给出了DROP INDEX的语法设计,但从源码实现看,该语句目前会在执行阶段抛出DROP INDEX is not supported.错误(见 SqlPlanImpl.java 中DropIndexPlan.execute的实现),属于尚未落地的语法占位。
二、SQL 语法总览
设计文档给出了CREATE INDEX的完整语法,同时给出了计划中的DROP INDEX语法:
CREATE INDEX [ IF NOT EXISTS ] index_name ON imap_name ( attribute_name [, ...] ) [ TYPE ( SORTED | HASH | BITMAP ) ] [ OPTIONS ( 'option_name' = 'option_value' [, ...] ) ]DROP INDEX [ IF EXISTS ] index_name ON imap_name从语法结构上可以看出三个关键设计点:
- 必须使用
ON imap_name指明索引挂在哪个 IMap 上。这是因为索引名index_name的作用域是map 级别(同一个集群中,不同 IMap 上的索引可以同名),这也是DROP INDEX语句也保留ON子句的原因。 - 索引直接绑定 IMap,而非绑定 SQL Mapping。
CREATE INDEX语句不接受 schema 限定,创建的索引也不会成为 catalog 中的独立对象。 - 索引不会出现在
information_schema中(当前版本)。
对应的语法解析定义位于 parserImpls.ftl(Hazelcast SQL 使用 Calcite 的 FreeMarker 语法模板生成解析器),其中SqlCreateIndex与SqlDropIndex两个规则分别处理这两类语句,构建出SqlCreateIndex/SqlDropIndex语法节点。
三、语句参数详解
1.IF NOT EXISTS
当指定该子句后,如果addIndex因索引已存在而抛出异常,该异常会被忽略,语句静默成功。注意:从 PlanExecutor.java 的实现看,IF NOT EXISTS的实现方式是"忽略已存在异常"——addIndex()调用本身在索引同名已存在时(即使配置不同)也不会做任何事。
而未指定IF NOT EXISTS时,执行端会做一次简单的存在性检查:如果同名索引已在getGlobalIndexRegistry()中注册,则抛出Can't create index: index '...' already exists错误。这种检查并非原子操作,因此两个客户端并发创建同名索引时理论上可能都成功——源码注释明确承认了这一限制,并指出 IMDG 中没有原子的创建索引操作,难以完全规避。
2. 索引列(attribute_name)
attribute_name是IMDG 索引 API 所定义的属性名,它可能与 SQL 中的列名并不完全一致,因为索引不感知 SQL 映射(Mapping)的存在。例如:
- 如果属性引用的是数组,索引会把数组中的每个元素作为独立的索引条目,全部指向包含该数组的 entry;
- 而在 SQL 中,数组类型目前完全不支持。
复合索引(Composite Index)同样受支持——在括号内列出多个属性即可。
3. 索引类型(TYPE)
IMDG 支持的三种索引类型全部可用于CREATE INDEX:
| 类型 | 说明 |
|---|---|
SORTED | 有序索引,支持范围查询与排序,默认类型(对应IndexConfig.DEFAULT_TYPE) |
HASH | 哈希索引,适用于等值查询 |
BITMAP | 位图索引,适用于低基数(cardinality)属性的快速过滤,支持额外选项 |
从 SqlCreateIndex.java 的getIndexType()方法可以看到,类型字符串会转为小写后匹配sorted/hash/bitmap三个关键字,其他任何值都会抛出错误:Can't create index: wrong index type. Only HASH, SORTED and BITMAP types are supported.
⚠️重要限制:SQL 引擎不支持 BITMAP 索引的扫描(scan),但支持 BITMAP 索引的创建。也就是说,你可以通过 SQL 为低基数字段建立 BITMAP 索引(供其他查询路径/谓词 API 使用),但 SQL 查询本身当前无法利用该 BITMAP 索引执行扫描。
4. OPTIONS 选项
选项(options)仅对 BITMAP 索引可用,因为位图索引拥有额外的BitmapIndexConfig。支持的选项只有两个:
unique_key:指定位图索引的唯一键(unique key),用于区分位图条目。若未指定,默认取__key(即KEY_ATTRIBUTE_NAME查询常量,对应 entry 的 key)。unique_key_transformation:指定唯一键的转换方式,取值对应 IMDG 的UniqueKeyTransformation枚举。测试用例中使用的值为OBJECT(见 SqlCreateIndexTest.java 中的OPTIONS ('unique_key' = '__key' , 'unique_key_transformation' = 'OBJECT')示例)。
指定未知选项会抛出错误。此外校验逻辑还包括:
- 如果索引类型不是 BITMAP 却提供了 options,抛出不支持错误;
- 列名重复(如
(this, this))抛错; - 同一选项名重复指定抛错;
OR REPLACE子句不被支持,会抛出not supported("OR REPLACE", "CREATE INDEX")错误。
执行端(PlanExecutor.java 的execute(CreateIndexPlan))会把这两个选项组装进BitmapIndexOptions:先取用户显式配置的值,缺省时unique_key回退到__key、unique_key_transformation回退到默认常量DEFAULT_UNIQUE_KEY_TRANSFORMATION,最后通过UniqueKeyTransformation.fromName(...)完成枚举转换并挂到IndexConfig上。
四、底层实现链路:从 SQL 到 IMap 索引
CREATE INDEX语句的完整执行链路如下(对应源码路径逐级递进):
- 语法解析:Calcite 解析器根据 parserImpls.ftl 中定义的
SqlCreateIndex规则,将 SQL 文本解析为SqlCreateIndex语法节点; - 语法校验:SqlCreateIndex.java 的
validate()方法完成类型、选项、列名去重等校验,并通过columns()/type()/options()/mapName()/indexName()等访问器暴露解析结果; - 计划生成:CalciteSqlOptimizerImpl.java 将
SqlCreateIndex节点转换为CreateIndexPlan(该计划不可缓存,且执行时不接受参数、不支持超时); - 计划执行:PlanExecutor.java 的
execute(CreateIndexPlan)完成最终落地,核心步骤为:- 通过
hazelcastInstance.getMap(plan.mapName())获取 IMap 的MapContainer; - 校验全局索引前提:若
mapContainer.shouldUseGlobalIndex()为 false(即分区索引),抛出INDEX_INVALID错误,提示必须开启集群属性ClusterProperty.GLOBAL_HD_INDEX_ENABLED(hazelcast.global.hd.index.enabled一类属性)才能通过 SQL 创建索引,因为SQL 目前无法使用分区索引; - 构建
IndexConfig:new IndexConfig(indexType, attributes).setName(indexName),BITMAP 类型时再附加BitmapIndexOptions; - 调用
IMap#addIndex(indexConfig)完成索引创建,返回更新计数结果(0)。
- 通过
SQL 文本 → Calcite 解析(parserImpls.ftl → SqlCreateIndex) → 语法校验(SqlCreateIndex#validate) → 计划生成(CalciteSqlOptimizerImpl → CreateIndexPlan) → 计划执行(PlanExecutor#execute) → IMap#addIndex(IndexConfig)全局索引要求(务必注意)
这是源码实现中最容易踩坑的约束:通过 SQL 创建的索引必须是全局索引(global index)。默认配置下分区存储使用分区索引,此时 SQLCREATE INDEX会直接失败,报错提示中明确给出了排障方向——将GLOBAL_HD_INDEX_ENABLED集群属性设为true。在设计文档的语法层面没有体现这一约束,但源码执行阶段强制执行。
五、权限模型
索引创建受ACTION_INDEX权限保护,该权限作用于目标 IMap。从实现看,CreateIndexPlan在执行前会通过安全上下文校验对应权限;相应地,DropIndexPlan的checkPermissions使用ACTION_DESTROY权限(虽然 DROP 尚未真正实现)。这意味着在启用安全(Security)的集群中,用户必须被授予目标 IMap 上的相应权限才能执行CREATE INDEX。
六、向后兼容性
该特性只是对既有索引实现(IMap 索引 API)的一层 SQL 封装,因此老版本客户端可以无缝使用。
在混合版本集群中:
- 如果命令落在 5.0 成员上,会抛出语法异常(类似
unexpected token: INDEX跟在CREATE关键字之后); - 如果命令落在 5.1 成员上,则即使集群中还有其他 5.0 成员,它们也能正常使用该特性。
无需针对版本做特殊处理。
七、测试验证
设计文档指出该特性以单元测试与浸泡测试(soak tests)覆盖即可。仓库中对应的测试文件为 SqlCreateIndexTest.java,其中的关键测试场景直接印证了文档中的各条语义:
- HASH 索引:
CREATE INDEX IF NOT EXISTS idx ON map (this) TYPE HASH创建后,查询计划从"未使用索引"变为"使用索引"(checkPlan(true, ...)); - SORTED 索引:默认类型验证,
CREATE INDEX ... (this) TYPE SORTED同样改变查询计划; - BITMAP 基础创建:
CREATE INDEX IF NOT EXISTS idx ON map (__key) TYPE BITMAP创建成功后可在getGlobalIndexRegistry().getIndex(indexName)中查到; - BITMAP 带选项创建:
OPTIONS ('unique_key' = '__key' , 'unique_key_transformation' = 'OBJECT')正常执行; - 空 map 上创建:索引创建不依赖已有数据;
- 默认类型(不写 TYPE):
CREATE INDEX IF NOT EXISTS idx ON map (this)使用默认 SORTED; - 重复列名:
(this, this)抛出包含specified more than once的错误。
八、未来演进方向
设计文档还规划了三个未来方向,目前均未实现,可作为理解设计意图的参考:
1. 面向不同连接器的索引
预期未来需要为ReplicatedMap等结构创建索引,为此计划在ON之后增加可选的CONNECTOR子句:
CREATE INDEX index_name ON imap_name ( attribute_name, ...) [ CONNECTOR IMap ]CONNECTOR子句为可选,IMap为默认连接器;该子句未来也需同步加入DROP INDEX命令。
2. 函数式索引(Function-Based Indexes)
SQL 世界中函数式索引(FBI)是指索引值为 SQL 表达式计算结果的情况,典型场景是大小写不敏感搜索:索引UPPER(column),查询用WHERE UPPER(column)=UPPER(?)。
IMap 本身不支持 FBI,但支持派生属性(derived attribute),可通过两种方式近似实现:
- 对 Java 序列化(
DataSerializable)对象,用户可添加返回派生值的 getter,后续查询用派生字段代替原始字段进行搜索; - 指定提取器(extractor),适用于所有序列化类型,包括 Portable 与 Compact 序列化。
SQL 引擎已支持第一种方式(自定义 getter 已被映射为字段),计划进一步映射 extractor 并允许这些列出现在查询中时使用索引。但要注意:这并非真正的 FBI——索引定义与查询中都不会出现 SQL 表达式。
3. 基于 Mapping 的索引(被否决的方案)
设计过程中曾考虑基于映射名创建索引,但最终被否决,理由包括:
- 索引生命周期不清晰:索引为映射而创建,但映射被删除后索引却依然存在;
- 映射与索引之间会产生依赖,而 catalog 存储缺少事务支持,无法正确实现(例如字段类型从 INT 改为 VARCHAR 时索引需要重建,或需要阻止映射的变更);
- 为映射创建的索引无法被同一 IMap 的其他映射使用,也无法被旧的谓词 API 使用;
- 会模糊"映射只是指向实际数据对象的轻量引用"这一设计理念。
如果未来实现基于映射的索引,可能采用的语法形式有:
CREATE INDEX index_name ON MAPPING mapping_name ... CREATE INDEX index_name ON mapping_name(...) CONNECTOR mapping ... CREATE MAPPING INDEX index_name ON mapping_name ...九、快速上手示例
综合以上语法与源码约束,给出一个可在 Hazelcast 5.1+ 集群中直接执行的完整示例序列:
-- 1. 准备数据(已有 map 的前提下) -- 需确保集群开启了全局索引:hazelcast.global.hd.index.enabled=true -- 2. 创建默认类型的 SORTED 索引 CREATE INDEX IF NOT EXISTS idx_name ON my_map (name); -- 3. 显式指定 HASH 索引 CREATE INDEX IF NOT EXISTS idx_hash ON my_map (id) TYPE HASH; -- 4. 创建复合 SORTED 索引 CREATE INDEX IF NOT EXISTS idx_composite ON my_map (last_name, first_name) TYPE SORTED; -- 5. 创建带选项的 BITMAP 索引 CREATE INDEX IF NOT EXISTS idx_bitmap ON my_map (__key) TYPE BITMAP OPTIONS ('unique_key' = '__key', 'unique_key_transformation' = 'OBJECT');执行上述语句后,可通过getGlobalIndexRegistry().getIndex(index_name)(成员端程序化方式)或直接运行针对性查询计划来验证索引已生效。注意:BITMAP 索引虽然可创建,但当前 SQL 查询无法利用其进行扫描。
参考链接
- 设计文档:docs/design/sql/12-create-index.md
- 语法解析(Calcite FMPP 模板):parserImpls.ftl
- 语法节点与校验:SqlCreateIndex.java
- 执行实现:PlanExecutor.java
- 执行计划定义:SqlPlanImpl.java
- 测试用例:SqlCreateIndexTest.java
- 缓存
- KV存储
- 消息队列
- 流处理
- 后端
【免费下载链接】hazelcast
Hazelcast is a unified real-time data platform combining stream processing with a fast data store, allowing customers to act instantly on>项目地址:https://gitcode.com/gh_mirrors/ha/hazelcast
相关推荐
Flink Hive 方言 LOAD DATA 语句完全指南:语法、参数与源码实现剖析
Flink Hive 方言 LOAD DATA 语句完全指南:语法、参数与源码实现剖析 LOAD DATA 是 Flink Hive 方言中用于将用户指定目录或
后端大数据流处理批处理PowerToys Awake 防休眠指南:3 种模式让电脑不再意外入睡
PowerToys Awake 防休眠指南:3 种模式让电脑不再意外入睡 下载卡在 99%、编译只剩最后几步,电脑却自己睡着了,进度停在原地。Windows 自
大数据数据分析批处理流处理机器学习图计算LanceDB Node.js Index 类完全指南:向量索引与标量索引的创建、参数调优与实战
LanceDB Node.js Index 类完全指南:向量索引与标量索引的创建、参数调优与实战 本文以 LanceDB JavaScript SDK( @la
数据库向量数据库全文检索人工智能