注册
merge into时报锁超时,排查发现是select导致的锁超时
专栏/Database Thinking/ 文章详情 /

merge into时报锁超时,排查发现是select导致的锁超时

胡li 2026/08/18 200 0 0
摘要 merge into执行时报锁超时,使用阻塞查询SQL排查发现是select导致的锁超时。怎么会,select会锁表吗?直接给出答案,是因为表中包括了位图索引,包含位图索引的表不支持并发的插入、删除和更新操作;

锁超时报错截图

image.png

位图索引导致 MERGE INTO 锁超时 —— 问题分析与示例

适用产品:达梦数据库(DM8 / DM9)| 错误码:-6407 锁超时
场景:表中存在位图索引,SELECT 查询(使用位图索引)与 MERGE INTO 并发时,MERGE INTO 报锁超时


一、问题现象

表上存在位图索引(BITMAP INDEX),业务中并发执行两类 SQL:

  • 查询类:`SELECT ...
  • 写入类MERGE INTO 该表 ...(目标表是带位图索引的表)

当两类 SQL 并发执行时,MERGE INTO 报 [-6407] 锁超时,查询也可能被连带阻塞。去掉位图索引后,问题消失。

-- 现象示例(会话 2 报错) MERGE INTO T_ORDER t USING (SELECT 4 AS ID, '0' AS STATUS, 400 AS AMOUNT FROM DUAL) s ON (t.ID = s.ID) WHEN MATCHED THEN UPDATE SET t.AMOUNT = s.AMOUNT WHEN NOT MATCHED THEN INSERT (ID, STATUS, AMOUNT) VALUES (s.ID, s.STATUS, s.AMOUNT); -- 结果:[-6407] 锁超时

二、根本原因

2.1 位图索引的锁机制(核心)

  • 位图索引按**键值位图段(bitmap segment)**加锁,锁粒度远大于行锁
  • 对位图索引列的任何 DML(INSERT / UPDATE / DELETE / MERGE),都会触发该列所有相关键值位图段的变更,需要获取位图段的排他锁(X 锁)
  • 达梦官方 FAQ 明确指出:对存在位图索引的表进行 INSERT 等操作时,会为该表加上独占锁,导致需要操作该表的其他事务(包括 SELECT)全部被阻塞;
  • 位图段读写锁机制:读取位图段持共享锁(S 锁),写入持排他锁(X 锁),S 锁与 X 锁互不相容

2.2 为什么 SELECT 会"卡住" MERGE INTO

MERGE INTO 本质是 UPDATE(匹配)+ INSERT(不匹配)的组合操作,落在位图索引列上时:

时间线 会话 1(查询) 会话 2(MERGE INTO)
T1 SELECT 使用位图索引扫描,对位图段加 S 锁(共享),长时间未释放
T2 需要修改位图段 → 申请 X 锁(排他)
T3 S 与 X 不相容 → 等待位图段锁释放
T4 查询事务迟迟不结束(大查询 / 未提交 / 报表任务) 等待超过 LOCK_WAIT_TIMEOUT(默认 10 秒)→ [-6407] 锁超时

反向同样成立:MERGE INTO 先执行且未提交时,会持有表的独占锁 / 位图段 X 锁,此时并发 SELECT 也会被阻塞。位图索引带来的锁冲突是双向的。


三、复现示例(完整可执行)

3.0 准备环境

-- 会话 0:建表 + 建位图索引 + 准备数据 DROP TABLE IF EXISTS T_ORDER; CREATE TABLE T_ORDER (ID INT PRIMARY KEY, STATUS VARCHAR(10), AMOUNT NUMBER(10,2)); -- 关键:在 MERGE 涉及的列上建位图索引(低基数列,看着"很适合"位图) CREATE BITMAP INDEX IDX_BM_STATUS ON T_ORDER(STATUS); INSERT INTO T_ORDER VALUES (1,'0',100),(2,'0',200),(3,'1',300); COMMIT;

3.1 会话 1:发起使用位图索引的长查询

-- 模拟"长时间占用":大事务内查询不提交,或报表类长查询

    SELECT /*+ INDEX(T_ORDER IDX_BM_STATUS) */ COUNT(*) FROM T_ORDER;
    DBMS_LOCK.SLEEP(30);  -- 持有锁 30 秒,模拟慢查询/未提交事务
    COMMIT;
END;

3.2 会话 2:执行 MERGE INTO(报锁超时)

-- 会话 2:并发执行 MERGE INTO(10 秒内必然报错) MERGE INTO T_ORDER t USING (SELECT 4 AS ID, '0' AS STATUS, 400 AS AMOUNT FROM DUAL) s ON (t.ID = s.ID) WHEN MATCHED THEN UPDATE SET t.AMOUNT = s.AMOUNT WHEN NOT MATCHED THEN INSERT (ID, STATUS, AMOUNT) VALUES (s.ID, s.STATUS, s.AMOUNT); -- 结果: -- 等待约 10 秒(LOCK_WAIT_TIMEOUT 默认值)后报错: -- [-6407] 锁超时

3.3 对照实验:去掉位图索引后正常

-- 会话 0:删除位图索引,换成普通 B-tree 索引 DROP INDEX IDX_BM_STATUS; CREATE INDEX IDX_BT_STATUS ON T_ORDER(STATUS); -- 普通索引,行级锁粒度 -- 重放 3.1 / 3.2 两个会话: -- MERGE INTO 正常执行(B-tree 索引只锁受影响的具体索引条目,不锁段/表) -- 问题消失 ✅

四、排查方法(现场定位)

出现 -6407 时,用以下 SQL 确认阻塞链是否指向位图索引表:

-- ① 看锁等待关系:谁在等谁 SELECT T.ID AS 等待事务, T.WAIT_FOR_ID AS 阻塞事务, L1.LMODE AS 期望锁, L2.LMODE AS 持锁方模式 FROM V$TRXWAIT T LEFT JOIN V$LOCK L1 ON T.ID = L1.TRX_ID LEFT JOIN V$LOCK L2 ON T.WAIT_FOR_ID = L2.TRX_ID; -- ② 定位锁对象是哪个表/索引,以及持锁会话和 SQL(官方推荐组合) SELECT A.*, B.NAME, C.SESS_ID FROM V$LOCK A LEFT JOIN SYSOBJECTS B ON B.ID = A.TABLE_ID LEFT JOIN V$SESSIONS C ON A.TRX_ID = C.TRX_ID; -- ③ 确认表上是否存在位图索引(嫌疑对象) SELECT OWNER, TABLE_NAME, INDEX_NAME, INDEX_TYPE FROM DBA_INDEXES WHERE INDEX_TYPE = 'BITMAP' AND TABLE_NAME IN ('T_ORDER'); -- 换成实际表名 -- ④ 查看锁超时相关参数 SELECT * FROM V$DM_INI WHERE PARA_NAME IN ('LOCK_WAIT_TIMEOUT','DDL_WAIT_TIME','BLDR_WAIT_TIME');
参数 说明 默认值
LOCK_WAIT_TIMEOUT 普通事务锁等待超时(秒) 10
DDL_WAIT_TIME DDL 锁等待超时(秒) 60
BLDR_WAIT_TIME 批量装载(dmfldr)锁等待超时(秒) 600

五、解决方案

方案 操作 适用场景 优先级
删除位图索引,改用 B-tree DROP INDEX IDX_BM_STATUS; CREATE INDEX IDX_BT_STATUS ON T_ORDER(STATUS); 有 MERGE / DML 并发的业务表 ⭐ 首选
读写分离 生产库不建位图索引;只读库 / 分析库(数据仓库副本)上建 统计查询需求强,但写入不能停 推荐
汇总表 / 物化视图 统计口径用独立汇总表,业务表不建位图 报表 / COUNT 类统计 可选
缩短查询事务 大查询 / 报表查询单独开只读事务,及时提交;避免长事务持锁 查询本身可优化 辅助
调大 LOCK_WAIT_TIMEOUT ALTER SYSTEM SET 'LOCK_WAIT_TIMEOUT'=60;(动态) 仅临时缓解,不治本 ⚠️ 慎用
关闭位图索引创建开关(新版本) 检查 ENABLE_CREATE_BM_INDEX_FLAG 参数 从源头防止误建位图索引 可选
-- 落地示例(首选方案) DROP INDEX IDX_BM_STATUS; -- 删位图索引 CREATE INDEX IDX_BT_STATUS ON T_ORDER(STATUS); -- 建普通 B-tree 索引 -- 若确需位图能力:仅在只读库/分析库上建 -- CREATE BITMAP INDEX IDX_BM_STATUS_RO ON T_ORDER(STATUS); -- 只读节点执行

六、预防与最佳实践

  1. 位图索引使用红线

    • ❌ 有 MERGE INTO / 频繁 UPDATE / INSERT 的表禁用位图索引
    • ❌ 键值多的列(人名列 / ID / 编码)禁用位图索引;
    • ✅ 只用于:低基数 + 读多写少 / 只读 + 数仓决策分析场景。
  2. MERGE INTO 前自查:目标表的 WHERE / 更新列上是否有位图索引?有则先评估并发风险。

  3. 锁监控常态化:定期查 V$LOCK + V$TRXWAIT,提前发现"位图段锁"类阻塞。

  4. 新版本可控制建索引开关:如参数 ENABLE_CREATE_BM_INDEX_FLAG(部分新版本支持),防止误建。

  5. 位图连接索引(BITMAP JOIN INDEX)同理:同一时间只允许一个事务更新关联表,OLTP + MERGE 场景一律禁用。


附:一句话速查

表上存在位图索引 + SELECT 查询 + MERGE INTO = 锁超时(-6407)。位图索引按"键值位图段"加锁(读 S / 写 X 互斥),MERGE INTO 一写一大片,和 SELECT 并发必然等锁。删掉位图索引换 B-tree,问题立解——OLTP 写入表(尤其有 MERGE 的表)永远不要用位图索引。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服