三门峡市网站建设_网站建设公司_Tailwind CSS_seo优化
2026/1/17 21:01:41 网站建设 项目流程

排它锁与共享锁详解 - 详解

2026-01-17 20:58  tlnshuju  阅读(0)  评论(0)    收藏  举报

排它锁与共享锁详解

1. 基本概念

共享锁(Shared Lock,S锁)

-- 显式加共享锁
SELECT * FROM table WHERE id = 1 LOCK IN SHARE MODE;

排它锁(Exclusive Lock,X锁)

-- 显式加排它锁
SELECT * FROM table WHERE id = 1 FOR UPDATE;

2. 锁的兼容性矩阵

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

3. 锁的作用与使用场景

共享锁(S锁)使用场景

-- 场景1:读取数据并确保不被修改
START TRANSACTION;
SELECT balance FROM accounts WHERE user_id = 1001 LOCK IN SHARE MODE;
-- 其他事务可以加共享锁读取,但不能修改
-- 确保在事务期间余额数据不被更改
COMMIT;
-- 场景2:检查引用完整性
START TRANSACTION;
-- 检查是否有订单引用此产品
SELECT COUNT(*) FROM orders WHERE product_id = 500 LOCK IN SHARE MODE;
-- 如果返回0,可以安全删除产品
DELETE FROM products WHERE id = 500;
COMMIT;

排它锁(X锁)使用场景

-- 场景1:更新操作(自动加排它锁)
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1001;
-- 其他事务不能读取(取决于隔离级别)或修改此行
COMMIT;
-- 场景2:悲观锁控制并发
START TRANSACTION;
SELECT * FROM inventory WHERE product_id = 2001 FOR UPDATE;
-- 检查库存并更新
UPDATE inventory SET quantity = quantity - 1 WHERE product_id = 2001;
COMMIT;
-- 场景3:防止幻读
START TRANSACTION;
SELECT * FROM orders WHERE user_id = 1001 AND status = 'pending' FOR UPDATE;
-- 在REPEATABLE READ隔离级别下,防止其他事务插入新的pending订单
INSERT INTO orders (...) VALUES (...);
COMMIT;

4. 锁的实现模型

InnoDB锁的内存结构

// 简化的锁结构表示
struct lock_t {
trx_t* trx;              // 持有锁的事务
lock_rec_t* rec_lock;    // 记录锁
lock_table_t* table_lock;// 表锁
uint32_t type_mode;      // 锁类型和模式
// ... 其他字段
};
struct lock_rec_t {
space_id_t space;        // 表空间ID
page_no_t page_no;       // 页号
uint32_t n_bits;         // 锁位图大小
byte* bits;              // 锁位图,记录哪些记录被锁住
};

锁管理的核心流程

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

5. 具体实现机制

记录锁(Record Lock)

-- 对单条记录加锁
SELECT * FROM users WHERE id = 1 FOR UPDATE;
-- 在id=1的记录上加排它锁

间隙锁(Gap Lock)

-- 防止幻读,锁定一个范围
SELECT * FROM users WHERE age BETWEEN 20 AND 30 FOR UPDATE;
-- 锁定age在20-30之间的间隙,防止其他事务插入

临键锁(Next-Key Lock)

-- 记录锁 + 间隙锁的组合
SELECT * FROM users WHERE id > 100 FOR UPDATE;
-- 锁定id>100的所有现有记录和间隙

6. 实际案例分析

银行转账场景

-- 事务1:转账操作
START TRANSACTION;
-- 对转出账户加排它锁
SELECT * FROM accounts WHERE account_no = 'A001' FOR UPDATE;
-- 对转入账户加排它锁  
SELECT * FROM accounts WHERE account_no = 'B002' FOR UPDATE;
-- 执行转账
UPDATE accounts SET balance = balance - 100 WHERE account_no = 'A001';
UPDATE accounts SET balance = balance + 100 WHERE account_no = 'B002';
COMMIT;

库存扣减场景

-- 高并发库存管理
START TRANSACTION;
-- 使用排它锁确保库存准确
SELECT quantity FROM inventory WHERE product_id = 1001 FOR UPDATE;
-- 检查库存
IF quantity >= order_quantity THEN
UPDATE inventory SET quantity = quantity - order_quantity
WHERE product_id = 1001;
COMMIT;
ELSE
ROLLBACK;
-- 库存不足处理
END IF;

7. 锁的监控与诊断

查看当前锁信息

-- 查看InnoDB锁状态
SHOW ENGINE INNODB STATUS\G
-- 查看锁信息部分
-- 查看当前锁等待
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 查看进程和锁信息
SHOW PROCESSLIST;

锁超时配置

-- 设置锁等待超时时间(秒)
SET SESSION innodb_lock_wait_timeout = 50;
SET GLOBAL innodb_lock_wait_timeout = 50;
-- 查看当前配置
SHOW VARIABLES LIKE 'innodb_lock_wait_timeout';

8. 死锁处理

死锁产生场景

-- 事务1
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 锁住id=1
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 等待id=2的锁
-- 事务2(同时执行)
START TRANSACTION;
UPDATE accounts SET balance = balance - 50 WHERE id = 2;  -- 锁住id=2  
UPDATE accounts SET balance = balance + 50 WHERE id = 1;  -- 等待id=1的锁
-- 死锁发生!

死锁检测与处理

-- InnoDB自动检测死锁并回滚代价较小的事务
-- 查看最近死锁信息
SHOW ENGINE INNODB STATUS\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()
except mysql.connector.errors.DatabaseError as e:
if 'Deadlock' in str(e):
# 死锁发生,重试逻辑
time.sleep(0.1)
retry_transaction()
else:
raise

9. 最佳实践

锁优化建议

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

锁与性能平衡

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

10. 总结

共享锁

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

排它锁

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

关键要点

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

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

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

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

立即咨询