十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

InnoDB Next-Key Lock / Gap Lock 测试总结

InnoDB Next-Key Lock / Gap Lock 测试总结 测试环境一、表结构和测试数据表结构测试数据二、基准加锁 SQL会话1全程不提交三、核心问题与结论Q1Next-Key Lock 会不会退化成 Record LockQ2锁的到底是stock 字段的区间还是别的Q3Gap 的左右边界谁大谁小是不是按 id 排Q4X 后面的记录锁锁的是间隙里所有记录吗Q5二级索引和聚簇索引的锁是不是联动的四、实测数据排序按四元组字典序五、加锁范围图解六、performance_schema.data_locks 实测验证LOCK_MODE 后缀判断规则二级索引 idx_category_status_stock 上的锁主键 PRIMARY 索引上的锁performance_schema.data_locks 数据七、验证过的具体场景关键推论第 4、5 条的原理八、验证过程验证前操作(会话1: 加锁)验证 No.1验证 No.2验证 No.3验证 No.4验证 No.5验证 No.6验证 No.7九、最终结论汇总测试环境环境MySQL 8.4.9 (InnoDB, REPEATABLE-READ)测试表product联合索引idx_category_status_stock (category_id, status, stock)一、表结构和测试数据表结构CREATETABLEproduct(idbigintunsignedNOTNULLAUTO_INCREMENTCOMMENT商品ID,category_idbigintunsignedNOTNULLCOMMENT所属分类ID,namevarchar(128)NOTNULLCOMMENT商品名称,pricedecimal(10,2)NOTNULLDEFAULT0.00COMMENT商品单价元,stockintunsignedNOTNULLDEFAULT0COMMENT库存数量,descriptionvarchar(500)DEFAULTCOMMENT商品描述,statustinyintNOTNULLDEFAULT1COMMENT状态1-上架0-下架,create_timedatetimeNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT创建时间,update_timedatetimeNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT更新时间,PRIMARYKEY(id),KEYidx_category_status_stock(category_id,status,stock))ENGINEInnoDBAUTO_INCREMENT16DEFAULTCHARSETutf8mb4COLLATEutf8mb4_0900_ai_ciCOMMENT商品表;关键点idx_category_status_stock是非唯一二级索引。测试数据INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(100,4,iPhone 15 Pro 256G,7999.00,120,A17 Pro芯片钛金属机身,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(200,4,华为Mate 60 Pro,6999.00,85,麒麟芯片卫星通话,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(300,4,小米14 Ultra,5999.00,200,徕卡光学骁龙8 Gen3,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(400,5,MacBook Pro 14英寸,14999.00,45,M3 Pro芯片18GB内存,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(500,5,联想拯救者Y9000P,8999.00,60,i9-14900HXRTX4070,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(600,5,罗技MX Master 3S鼠标,799.00,300,静音按键8000DPI,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(700,6,优衣库纯棉T恤,99.00,500,100%纯棉舒适透气,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(800,6,李维斯501牛仔裤,599.00,150,经典直筒水洗做旧,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(900,6,耐克Air Jordan 1,1299.00,80,高帮复古篮球鞋,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(1000,7,ZARA碎花连衣裙,399.00,220,雪纺面料收腰设计,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(1100,7,优衣库羊毛大衣,899.00,90,70%羊毛含量中长款,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(1200,8,烟台红富士苹果5斤,39.90,1000,脆甜多汁新鲜采摘,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(1300,8,海南金煌芒果5斤,49.90,600,果肉细腻核小无丝,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(1400,9,三只松鼠坚果大礼包,89.00,450,7种坚果每日坚果,1,2026-08-24 14:37:17,2026-08-26 19:27:08);INSERTINTOproduct(id,category_id,name,price,stock,description,status,create_time,update_time)VALUES(1500,9,可口可乐330ml*24罐,59.90,800,经典口味整箱装,0,2026-08-24 14:37:17,2026-08-26 19:27:08);二、基准加锁 SQL会话1全程不提交SELECT*FROMproductWHEREcategory_id4ANDstatus1ANDstock120FORUPDATE;三、核心问题与结论Q1Next-Key Lock 会不会退化成 Record Lock结论不会。退化条件必须同时满足使用唯一索引主键 / unique key等值查询且能唯一确定一行本例中idx_category_status_stock是普通联合索引非唯一且stock 120是范围条件两个条件都不满足因此全程走 Next-Key LockRecord Lock Gap Lock。补充即使把stock 120换成stock 120因为索引本身非唯一可能存在多行同值仍然不会退化。Q2锁的到底是stock 字段的区间还是别的结论锁的是联合索引(category_id, status, stock)在 B 树上按字典序排列的物理相邻区间不是单独 stock 字段的取值区间。对于非唯一二级索引InnoDB 会在索引 key 末尾自动追加主键值做唯一标识和排序真实排序键是四元组(category_id, status, stock, id)Q3Gap 的左右边界谁大谁小是不是按 id 排结论不是。排序优先级严格按索引定义顺序id只是最后的 tie-breaker决胜属性。即先比category_id相同再比status相同再比stock只有前三者都相同时才比较id。id 数值大小本身不影响排序位置除非前三个字段完全相同。Q4X后面的记录锁锁的是间隙里所有记录吗结论不是。只锁LOCK_DATA标出的那一条具体记录间隙本身不含任何行Gap 锁锁的是这段空白阻止插入。Q5二级索引和聚簇索引的锁是不是联动的结论不是。只有真正满足 WHERE 全部条件、会被返回/修改的行才会在聚簇索引PRIMARY上加锁仅作为扫描边界经过的行只在二级索引层加锁聚簇索引上完全干净。四、实测数据排序按四元组字典序(4, 1, 85, 200) ← 华为 Mate 60 Pro (4, 1, 120, 100) ← iPhone 15 Pro ← stock120 命中起点 (4, 1, 200, 300) ← 小米 14 Ultra (5, 1, 45, 400) ← MacBook Pro 14英寸 ← 扫描终止边界 (5, 1, 60, 500) ← 联想拯救者 Y9000P ← 未被扫描/未锁五、加锁范围图解(4,1,85,200) ──gapA── (4,1,120,100) ──gapB── (4,1,200,300) ──gapC── (5,1,45,400) ──未锁── (5,1,60,500) ↑ recordgapA ↑ recordgapB ↑ recordgapC规则扫描命中的每条记录都加Next-Key Lock记录锁 记录前面的间隙锁遇到第一条不满足WHERE 条件的记录(5,1,45,400)category_id 已经变成 5时仍会锁住这条记录 它前面的 gap然后扫描停止该记录之后的 gap(5,1,45,400)到(5,1,60,500)之间完全不会被锁六、performance_schema.data_locks 实测验证LOCK_MODE 后缀判断规则LOCK_MODE含义X无后缀Next-Key Lock 记录锁 记录前面的 Gap 锁X,REC_NOT_GAP纯记录锁不含 gap只锁这一行本身X,GAP纯 Gap 锁不锁记录本身本例中没出现二级索引idx_category_status_stock上的锁LOCK_DATALOCK_MODE含义4, 1, 120, 100XNext-Key Lock记录 前置 gap4, 1, 200, 300XNext-Key Lock记录 前置 gap5, 1, 45, 400XNext-Key Lock记录 前置 gap(4,1,85,200)未出现独立记录 —— 它的记录锁隐含在(4,1,120,100)的前置 gap里。(5,1,60,500)完全没出现 —— 印证扫描确实止步于(5,1,45,400)。主键 PRIMARY 索引上的锁LOCK_DATALOCK_MODE含义100X,REC_NOT_GAP纯记录锁无 gap300X,REC_NOT_GAP纯记录锁无 gapid400MacBook Pro在 PRIMARY 上完全无锁。performance_schema.data_locks 数据结论二级索引层——只要扫描到就加锁用于界定扫描边界、防幻读聚簇索引层——只有真正完全满足 WHERE 条件、会被返回/修改的行才会回表加锁。LOCK_MODE的REC_NOT_GAP后缀就是判断纯记录锁 vs Next-Key Lock的直接依据。七、验证过的具体场景#场景条件(category_id, status, stock, id)落入的 Gap是否阻塞原因1Insert(4, 1, 100, 新id)gapA/gapB 区间内✅ 阻塞stock100 落在 85~120 之间2Insert(4, 1, 999, 新id)gapC边界 gap✅ 阻塞落在(4,1,200,300)到(5,1,45,400)之间3Insert(6, 1, 100, 新id)无关区间❌ 不阻塞category_id6 完全不在锁定范围内4Insert(5, 1, 45, 新id)新 id 400(5,1,45,400)之后的未锁 gap❌ 不阻塞自增 id 必然更大排在已锁记录之后落入未扫描区间5Insert(4, 1, 85, 10000)gapA 内✅ 阻塞前三段与(4,1,85,200)相同id10000200 排其后但仍在 gapA 与(4,1,120,100)之间6SELECT FOR UPDATE(,,,400)PRIMARY 索引未锁❌ 不阻塞该行只在二级索引加锁聚簇索引干净7SELECT FOR UPDATE(5, 1, 45,)二级索引命中记录锁✅ 阻塞超时精确命中(5,1,45,400)的 Record Lock 部分关键推论第 4、5 条的原理能否被阻塞看的是新 key 落在哪个 gap不是看 id 数值本身大小。自增主键保证新插入行的 id 一定大于表中已有 id因此在(category_id,status,stock)相同的情况下新行必然排在已有同值记录之后。如果这个之后的位置恰好是已扫描并加锁的 gap 内部如(4,1,85,10000)则阻塞如果这个之后的位置落在扫描已经停止、未被触及的 gap如(5,1,45,新id)则不阻塞。八、验证过程验证前操作(会话1: 加锁)验证 No.1会话2: Insert数据performance_schema.data_locks 数据验证 No.2会话2: Insert数据performance_schema.data_locks 数据验证 No.3会话2: Insert数据performance_schema.data_locks 数据验证 No.4会话2:performance_schema.data_locks 数据验证 No.5会话2:performance_schema.data_locks 数据验证 No.6会话2:performance_schema.data_locks 数据补充id400 那行只在二级索引 idx_category_status_stock 上被锁因为它是扫描边界用于确定 Next-Key Lock 的范围从未在 PRIMARY 索引上加锁data_locks 里 PRIMARY 只有 100、300 两条。窗口2 直接走 PRIMARY 查找检测不到冲突加锁成功。验证 No.7会话2:performance_schema.data_locks 数据补充这是纯等值查询在 idx_category_status_stock 上精确定位到的 key 正是 (5,1,45,400)——与窗口1 持有的那条 Next-Key Lock 完全相同直接命中它的 Record Lock 部分产生冲突最终等待超时。九、最终结论汇总非唯一联合索引下FOR UPDATE走范围查询不会退化为 Record Lock全程 Next-Key Lock。非唯一二级索引的真实排序键是(索引定义字段..., 主键id)id 只是最后的 tie-breaker不是第一排序依据。Gap 锁的边界由索引字段的物理排序位置决定不是 WHERE 条件里某字段的逻辑取值范围。扫描到第一条不满足 WHERE 条件的记录后即停止该记录之后的区域不会被锁——这是看似应该锁却没锁的常见原因。Next-Key Lock 里的记录锁只锁LOCK_DATA标出的那一条具体记录间隙锁只锁两条相邻记录之间的空白不会波及间隙里面的其他行因为间隙内本就没有行。二级索引和聚簇索引的锁是完全独立的两套锁空间只有真正满足 WHERE 全部条件的行才会在聚簇索引上加锁仅作为扫描边界经过的行只在二级索引层留痕可以被其他事务通过主键正常访问不会阻塞。判断某次操作是否会被阻塞本质是判断它访问的索引树和索引 key是否与已有锁重叠记录锁重叠 or 落入某个 gap不能凭直觉推断。performance_schema.data_locks是验证以上结论的直接手段通过INDEX_NAME区分锁在哪棵树上通过LOCK_MODE有无REC_NOT_GAP后缀判断是纯记录锁还是 Next-Key Lock。
返回列表