news 2026/4/12 11:38:07

排它锁与共享锁详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
排它锁与共享锁详解

排它锁与共享锁详解

1. 基本概念

共享锁(Shared Lock,S锁)

-- 显式加共享锁SELECT*FROMtableWHEREid=1LOCKINSHAREMODE;

排它锁(Exclusive Lock,X锁)

-- 显式加排它锁SELECT*FROMtableWHEREid=1FORUPDATE;

2. 锁的兼容性矩阵

当前锁状态共享锁(S)排它锁(X)无锁
共享锁(S)✅ 兼容❌ 冲突✅ 兼容
排它锁(X)❌ 冲突❌ 冲突✅ 兼容
无锁✅ 兼容✅ 兼容✅ 兼容

3. 锁的作用与使用场景

共享锁(S锁)使用场景

-- 场景1:读取数据并确保不被修改STARTTRANSACTION;SELECTbalanceFROMaccountsWHEREuser_id=1001LOCKINSHAREMODE;-- 其他事务可以加共享锁读取,但不能修改-- 确保在事务期间余额数据不被更改COMMIT;-- 场景2:检查引用完整性STARTTRANSACTION;-- 检查是否有订单引用此产品SELECTCOUNT(*)FROMordersWHEREproduct_id=500LOCKINSHAREMODE;-- 如果返回0,可以安全删除产品DELETEFROMproductsWHEREid=500;COMMIT;

排它锁(X锁)使用场景

-- 场景1:更新操作(自动加排它锁)STARTTRANSACTION;UPDATEaccountsSETbalance=balance-100WHEREuser_id=1001;-- 其他事务不能读取(取决于隔离级别)或修改此行COMMIT;-- 场景2:悲观锁控制并发STARTTRANSACTION;SELECT*FROMinventoryWHEREproduct_id=2001FORUPDATE;-- 检查库存并更新UPDATEinventorySETquantity=quantity-1WHEREproduct_id=2001;COMMIT;-- 场景3:防止幻读STARTTRANSACTION;SELECT*FROMordersWHEREuser_id=1001ANDstatus='pending'FORUPDATE;-- 在REPEATABLE READ隔离级别下,防止其他事务插入新的pending订单INSERTINTOorders(...)VALUES(...);COMMIT;

4. 锁的实现模型

InnoDB锁的内存结构

// 简化的锁结构表示structlock_t{trx_t*trx;// 持有锁的事务lock_rec_t*rec_lock;// 记录锁lock_table_t*table_lock;// 表锁uint32_ttype_mode;// 锁类型和模式// ... 其他字段};structlock_rec_t{space_id_tspace;// 表空间IDpage_no_tpage_no;// 页号uint32_tn_bits;// 锁位图大小byte*bits;// 锁位图,记录哪些记录被锁住};

锁管理的核心流程

兼容
不兼容
发现死锁
无死锁
超时
未超时
事务请求锁
检查锁兼容性
立即授予锁
进入等待队列
死锁检测
回滚代价小的事务
等待锁释放
继续执行
被回滚事务重试
锁超时检查
返回锁超时错误
等待锁释放通知

5. 具体实现机制

记录锁(Record Lock)

-- 对单条记录加锁SELECT*FROMusersWHEREid=1FORUPDATE;-- 在id=1的记录上加排它锁

间隙锁(Gap Lock)

-- 防止幻读,锁定一个范围SELECT*FROMusersWHEREageBETWEEN20AND30FORUPDATE;-- 锁定age在20-30之间的间隙,防止其他事务插入

临键锁(Next-Key Lock)

-- 记录锁 + 间隙锁的组合SELECT*FROMusersWHEREid>100FORUPDATE;-- 锁定id>100的所有现有记录和间隙

6. 实际案例分析

银行转账场景

-- 事务1:转账操作STARTTRANSACTION;-- 对转出账户加排它锁SELECT*FROMaccountsWHEREaccount_no='A001'FORUPDATE;-- 对转入账户加排它锁SELECT*FROMaccountsWHEREaccount_no='B002'FORUPDATE;-- 执行转账UPDATEaccountsSETbalance=balance-100WHEREaccount_no='A001';UPDATEaccountsSETbalance=balance+100WHEREaccount_no='B002';COMMIT;

库存扣减场景

-- 高并发库存管理STARTTRANSACTION;-- 使用排它锁确保库存准确SELECTquantityFROMinventoryWHEREproduct_id=1001FORUPDATE;-- 检查库存IFquantity>=order_quantityTHENUPDATEinventorySETquantity=quantity-order_quantityWHEREproduct_id=1001;COMMIT;ELSEROLLBACK;-- 库存不足处理ENDIF;

7. 锁的监控与诊断

查看当前锁信息

-- 查看InnoDB锁状态SHOWENGINEINNODBSTATUS\G-- 查看锁信息部分-- 查看当前锁等待SELECT*FROMinformation_schema.INNODB_LOCKS;SELECT*FROMinformation_schema.INNODB_LOCK_WAITS;-- 查看进程和锁信息SHOWPROCESSLIST;

锁超时配置

-- 设置锁等待超时时间(秒)SETSESSIONinnodb_lock_wait_timeout=50;SETGLOBALinnodb_lock_wait_timeout=50;-- 查看当前配置SHOWVARIABLESLIKE'innodb_lock_wait_timeout';

8. 死锁处理

死锁产生场景

-- 事务1STARTTRANSACTION;UPDATEaccountsSETbalance=balance-100WHEREid=1;-- 锁住id=1UPDATEaccountsSETbalance=balance+100WHEREid=2;-- 等待id=2的锁-- 事务2(同时执行)STARTTRANSACTION;UPDATEaccountsSETbalance=balance-50WHEREid=2;-- 锁住id=2UPDATEaccountsSETbalance=balance+50WHEREid=1;-- 等待id=1的锁-- 死锁发生!

死锁检测与处理

-- InnoDB自动检测死锁并回滚代价较小的事务-- 查看最近死锁信息SHOWENGINEINNODBSTATUS\G-- 在LATEST DETECTED DEADLOCK部分查看详情-- 错误处理代码示例(应用程序层面)try:cursor.execute("UPDATE accounts SET balance = balance - 100 WHERE id = 1")cursor.execute("UPDATE accounts SET balance = balance + 100 WHERE id = 2")connection.commit()exceptmysql.connector.errors.DatabaseErrorase:if'Deadlock'instr(e):# 死锁发生,重试逻辑time.sleep(0.1)retry_transaction()else: raise

9. 最佳实践

锁优化建议

-- 1. 尽量使用索引,减少锁范围-- 慢:全表扫描,锁住所有记录SELECT*FROMusersWHEREnameLIKE'%john%'FORUPDATE;-- 快:使用索引,只锁相关记录SELECT*FROMusersWHEREid=1001FORUPDATE;-- 2. 保持事务简短STARTTRANSACTION;-- 尽快完成数据操作UPDATE...;DELETE...;COMMIT;-- 立即提交释放锁-- 3. 按固定顺序访问资源,避免死锁-- 所有事务都按id升序访问账户UPDATEaccountsSET...WHEREid=1;UPDATEaccountsSET...WHEREid=2;-- 4. 使用较低的隔离级别(如READ COMMITTED)减少锁竞争SETSESSIONTRANSACTIONISOLATIONLEVELREADCOMMITTED;

锁与性能平衡

-- 读多写少场景:考虑使用乐观锁SELECTversion,dataFROMtableWHEREid=1;-- 应用程序处理业务逻辑UPDATEtableSETdata=new_data,version=version+1WHEREid=1ANDversion=old_version;-- 如果影响行数为0,说明版本已变化,需要重试-- 写多场景:使用悲观锁但控制粒度-- 使用行级锁而不是表锁SELECT*FROMtableWHEREid=1FORUPDATE;-- 而不是:LOCK TABLES table WRITE;

10. 总结

共享锁

  • 用于读取操作,允许多个事务同时读取
  • 阻止其他事务获取排它锁
  • 适合读多写少的并发读取场景

排它锁

  • 用于写入操作,确保数据一致性
  • 阻止其他事务获取共享锁或排它锁
  • 适合数据更新、删除等修改操作

关键要点

  • 理解锁兼容性是设计并发系统的关键
  • 合理选择锁粒度(行锁 vs 表锁)
  • 监控和优化锁等待时间
  • 设计事务以避免死锁
  • 根据业务场景选择合适的锁策略

正确使用锁机制可以确保数据一致性,同时最大化系统并发性能。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!