Mysql 死锁、乐观锁、悲观锁

47 阅读3分钟

悲观锁

定义:假设数据在并发访问数据时总会发生冲突,因此访问数据前,会强制加锁,其他事务必须等待或阻塞,直到当前事务完成。

乐观锁

定义:假设数据冲突很少发生,因此不加锁,而是通过数据版本控制来判断当前是否有其他事务在修改数据,当提交更新时,检查在此期间是否被人改动过,如果有修改就更新失败;如果没有修改,才允许提交。

  • 不加数据库锁,适用于高并发
  • 通过版本号(version字段)控制数据的一致性
  • 数据一致性依赖业务逻辑,而不是数据库锁 例子:
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  balance DECIMAL(10, 2),
  version INT DEFAULT 0
);

# 1.获取当前记录和版本号
SELECT balance,version FROM users WHERE id = 1
# 假设这里查处来数据为 balance = 400 version = 3 


# 2.在更新时检查版本好是否未被改动
UPDATE users SET balance = balance - 100, version = version + 1 
WHERE  id = 1 AND version = 3

# 3.判断是否成功
# 返回1: 更新行数为1,表示版本号一致,更新成功
# 返回0: 表示其他事务已修改数据,版本号不一致,更新失败

死锁

定义:两个或多个事务并发执行的过程中,相互持有对方需要的资源,并且永远相互等待,导致无法继续执行。 如何理解呢?

  • 事务A锁了资源1,在事务过程中想要资源2
  • 事务B锁了资源2,在事务过程中要想资源1
  • A、B两个事务都不释放自己已有的锁,于是乎直接卡住,就发生了死锁 例子:(并发执行)
CREATE TABLE users (
  id INT PRIMARY KEY,
  name VARCHAR(50),
  balance DECIMAL(10, 2)
);
# 数据表中有两条数据
# id = 1 name = '路人甲' balance = 500
# id = 2 name = '路人乙' balance = 800
# 事务A
START TRANSACTION;

UPDATE users SET balance = balance - 100 WHERE id = 1 
# 此时事务A已锁住 id = 1

UPDATE use s SET balance = balance - 100 WHERE id = 2
# 尝试获取 id = 2 可能被事务B锁住,进入等待
# 事务B
START TRANSACTION;

UPDATE users SET balance = balance + 200 WHERE id = 2
# 此时事务B已锁住 id = 2

UPDATE users SET balance = balance + 200 WHERE id = 1
# 尝试获取 id = 1,发现被事务A锁主,进入等待 

两个事务相互陷入相互等待,构成死锁。
死锁的本质:多个事务多个资源并发竞争时产生的相互等待。

如何避免死锁:

  • 设置事务等待锁超时时间:当一个事务的等待时间超过该值后,就对这个事务进行回滚,然后释放锁,让另一个事务继续执行。可设置参数innodb_lock_wait_timeout,默认50s。
  • 开启主动死锁检测:主动死锁检测在发生死锁后,主动回滚锁链上的某一个事务,让其他事务可以继续执行。可设置参数innodb_deadlock_detect设置为no,表示开启。