☰
Hazelcast SQL 创建 IMap 索引(CREATE INDEX)完整指南:语法、参数与源码实现剖析
2026/10/9 5:04:04 网站建设 项目流程

导读:本文基于 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

从语法结构上可以看出三个关键设计点:

  1. 必须使用ON imap_name指明索引挂在哪个 IMap 上。这是因为索引名index_name的作用域是map 级别(同一个集群中,不同 IMap 上的索引可以同名),这也是DROP INDEX语句也保留ON子句的原因。
  2. 索引直接绑定 IMap,而非绑定 SQL Mapping。CREATE INDEX语句不接受 schema 限定,创建的索引也不会成为 catalog 中的独立对象。
  3. 索引不会出现在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。支持的选项只有两个:

  1. unique_key:指定位图索引的唯一键(unique key),用于区分位图条目。若未指定,默认取__key(即KEY_ATTRIBUTE_NAME查询常量,对应 entry 的 key)。
  2. 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语句的完整执行链路如下(对应源码路径逐级递进):

  1. 语法解析:Calcite 解析器根据 parserImpls.ftl 中定义的SqlCreateIndex规则,将 SQL 文本解析为SqlCreateIndex语法节点;
  2. 语法校验:SqlCreateIndex.java 的validate()方法完成类型、选项、列名去重等校验,并通过columns()/type()/options()/mapName()/indexName()等访问器暴露解析结果;
  3. 计划生成:CalciteSqlOptimizerImpl.java 将SqlCreateIndex节点转换为CreateIndexPlan(该计划不可缓存,且执行时不接受参数、不支持超时);
  4. 计划执行: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

点击查看免费下载

相关推荐

上一篇:COM3D2.MaidFiddler完全指南:如何成为女仆管理大师
下一篇:Reloaded-II终极指南:如何快速搭建跨平台游戏修改框架

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询