避免死锁的唯一键索引-Panda Index

PolarDB-X存储引擎提供了新一代多版本唯一键索引-Panda Index 。针对传统MySQL非聚簇索引的效能瓶颈,Panda Index 通过原生的多版本并发控制(MVCC)能力,避免查询路径上跨索引访问带来的额外开销,且将全局区间约束检测优化为行级锁粒度,有效规避锁范围扩散问题。

前提条件

您的实例需满足以下版本要求:

  • 实例系列标准版企业版

  • 引擎版本MySQL 8.0

  • 存储节点版本:xcluster8.4.20-20250527及以上版本,即2025-05-27发布的版本及之后版本。

    说明

计费说明

Panda Index 功能本身是免费的。然而,Panda Index 在每个唯一索引记录上会增加28个字节。对于绝大多数业务表而言,这部分存储空间的增加通常低于5%,但仍会增加相应的存储费用。

注意事项

  • 实例版本:由于Panda Index 涉及底层存储结构变动,使用了Panda Index 的实例无法降级至不支持Panda Index 的老版本。

  • 存量索引处理:对于已存在唯一键(UK)的表,在开启Panda Index 后,它们不会自动转换为Panda Index 。您可以通过以下方式重建索引,以使其具备Panda Index 能力。

    1. 创建新索引:ALTER TABLE ... ADD UNIQUE INDEX idx_new ...;

    2. 更换索引名:ALTER TABLE ... RENAME idx TO idx_old, RENAME idx_new TO idx;

    3. 删除原索引:ALTER TABLE ... DROP INDEX idx_old;

  • 隔离级别限制Panda Index 主要优化Read-Committed(RC)隔离级别下的Gap锁问题。在Repeatable-Read(RR)隔离级别下,为保证可重复读,系统仍会使用Next-Key锁(记录锁+Gap锁),因此Panda Index 在此级别下无法避免Gap锁。

  • 表与索引类型限制Panda Index 不适用于临时表、系统表、压缩表以及多值索引。

如何使用Panda Index

1. 开启Panda Index 功能

前往PolarDB分布式版控制台,在目标集群的配置与管理 > 参数设置页面的存储层页签中,将参数opt_index_format_panda_enabled的值修改为ON。此操作无需重启实例,即时生效。

image

说明
  • 将参数opt_index_format_panda_enabled的值修改为ON,创建表或者新建唯一键索引时默认创建Panda Index

  • 将参数opt_index_format_panda_enabled的值修改为OFF,创建表或者新建唯一键索引时默认创建与MySQL社区保持一致的普通唯一键索引形式。

2. 验证Panda Index 效果

您可以通过一个简单的并发场景来验证其效果。

数据准备

-- 创建表与唯一键索引
CREATE TABLE t1(
  id int,
  c1 int,
  PRIMARY KEY(id),
  UNIQUE KEY uk1(c1)
) SINGLE /* 这里以企业版的单表为例,标准版实例不需要 */;

-- 插入数据
INSERT INTO t1 VALUES (1,1);
INSERT INTO t1 VALUES (100,100);

并发测试

在一个事务(Session 1)中删除一条记录并插入一条新记录,模拟常见的“先删后改”操作。

BEGIN;
DELETE FROM t1 where id=1;
INSERT INTO t1 values (2,1);

同时,在另一个事务(Session 2)中插入一条不冲突但位于间隙内的数据。

INSERT INTO t1 values (3,2);

此时,普通唯一键与Panda Index 表现各不相同。

  • 普通唯一键:Session 2的插入操作会因等待Session 1持有的Gap锁而超时失败。

    mysql> INSERT INTO t1 values (3,2);
    
    -- 报错中出现关键字:Lock wait timeout exceeded; try restarting transaction

    标准版实例可以观察到锁信息中包含普通唯一键uk1上的GAP锁。

    SELECT lock_data, lock_mode FROM performance_schema.data_locks WHERE index_name ='uk1';
    +-----------+---------------+
    | lock_data | lock_mode     |
    +-----------+---------------+
    | 1, 1      | X,REC_NOT_GAP |
    | 1, 1      | S,GAP         |
    | 100, 100  | S,GAP         |
    | 1, 2      | S,GAP         |
    +-----------+---------------+
  • Panda Index :Session 2的插入操作会立即成功,因为Panda Index 不会产生不必要的Gap锁。

    mysql> INSERT INTO t1 values (3,2);
    
    Query OK, 1 row affected (0.00 sec)

    标准版实例可以观察到锁信息中并无普通唯一键uk1上的GAP锁。

    SELECT lock_data, lock_mode FROM performance_schema.data_locks WHERE index_name ='uk1';
    +-----------+---------------+
    | lock_data | lock_mode     |
    +-----------+---------------+
    | 1, 2      | X,REC_NOT_GAP |
    +-----------+---------------+

常见问题

我的老版本实例升级后,存量表的普通唯一键(Unique Key)会自动升级为Panda Index 吗?

Panda Index 对底层存储结构有改动,实例升级后需重建老的普通唯一键(Unique Key)才能升级为Panda Index 。即通过ALTER TABLE ... DROP INDEX ...; ALTER TABLE ... ADD UNIQUE INDEX ...;命令进行重建。

为什么升级为Panda Index 后,依然发现了涉及普通唯一键(Unique Key)GAP锁的死锁场景?

请检查您的隔离级别是否是Repeatable-Read。

Panda Index 在锁系统层面主要消除的是MySQL社区在Read-Committed(RC)隔离级别下,出于保证唯一键约束的目的,在某些场景下必须加的GAP锁(详细讨论可见 Bug #68021)。Repeatable-Read(RR)隔离级别由于要保证可重复读的隔离级别语义,对记录普遍施加Next-Key锁(即记录锁+GAP锁)的模式无法避免,因此Panda Index 无法避免此级别下的GAP锁死锁。

开启Panda Index 参数opt_index_format_panda_enabled有风险吗?

没有风险。开启或关闭该参数均无需重启实例,对现有业务无任何影响。

Panda Index 支持哪些表类型和数据库模式?

无论是标准版还是企业版,均支持Panda Index

企业版中,无论是分区表、单表还是广播表,亦或是DRDS模式或AUTO模式的数据库,均支持Panda Index

使用Panda Index 会有性能开销吗?

没有额外的性能开销,在各项benchmark测试中,Panda Index 的性能均获得了更优的结果。其主要代价是微小(通常低于5%)的存储空间增长