悲观锁
定义:假设数据在并发访问数据时总会发生冲突,因此访问数据前,会强制加锁,其他事务必须等待或阻塞,直到当前事务完成。
乐观锁
定义:假设数据冲突很少发生,因此不加锁,而是通过数据版本控制来判断当前是否有其他事务在修改数据,当提交更新时,检查在此期间是否被人改动过,如果有修改就更新失败;如果没有修改,才允许提交。
- 不加数据库锁,适用于高并发
- 通过版本号(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,表示开启。