Mysql系列进步一: MySQL 实现乐观锁和悲观锁

[OTC](Mysql系列进步一: MySQL 实现乐观锁和悲观锁)

前言

基于数据库的乐观锁和悲观锁主要目的是为了解决在数据库并发时, 对数据更新不一致导致的问题.

悲观锁

悲观锁具有独占和排他两种特性, 修改数据时必须先拿到锁, 否则直接拒绝. 在操作数据的过程中, 全称持有锁, 操作完毕后, 释放锁.

实现悲观锁

一般在MySQL中实现悲观锁, 使用 select … for update 实现, 当一个事务对某资源调用了该语句, 其他所有调用该资源的事务只有等待, 直到第一个事务释放.

# 1. 构造测试表
CREATE TABLE `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert into test (name,age) values('zhao',20);
insert into test (name,age) values('xi',21);

# 2. 设置数据库不自动提交
set autocommit = 0;

# 3. 事务1 执行
select * from test where id = 1 for update; 

# 4. 事务2 执行
update test set age = 22 where id = 1;
commit;

## 可以观察事务2 会一直阻塞在update 语句

# 5. 在事务1 执行 commit;

## 可以观察事务2 在事务1执行完毕后, 事务2也执行成功.

for update 悲观锁的问题

当 for update的字段为索引或者主键的时候, 只锁住索引或者主键对应的行, 否则锁住整个表, 使用时一定注意.


# 1. 事务1 执行
select * from test where name = 'zhao' for update;

# 2. 事务2 执行
select * from test where name = 'xi' for update;

## 可以观察事务2 会一直阻塞

# 3. 事务1 执行 rollback

## 观察事务2 在事务1 执行完毕后, 事务2也执行成功

# 4. 为age字段添加索引
create index idx_test_age on test(age);
commit;

# 5. 事务1 执行
select * from test where age = 22 for update;

# 6. 事务2 执行
select * from test where age = 21 for update;
commit;

## 可以观察事务2 并不会阻塞

# 7. 事务1 执行 rollback
rollback;

# 8. 设置数据库自动提交
set autocommit = 1;

悲观锁的弱化版本

悲观锁也可以使用 select … lock in share mode 来实现, 不过相对select … for update 来说, 前者不会阻塞查询. 因为 前者使用 IS (意向共享锁) 锁, 后则使用 IX (意向排他锁) 锁.
下面验证下 select … lock in share mode.

# 1. 设置数据库不自动提交
set autocommit = 0;

# 2. 事务1 执行
select * from test where id = 1 lock in share mode; 

# 3. 事务2 执行
select * from test where id = 1 lock in share mode;  
commit;

## 可以观察事务2 并没有阻塞

# 4. 事务2 执行
update test set age = 23 where id = 1;
commit;
 
## 可以观察事务2 会阻塞在update语句

# 5. 事务1 执行rollback;
rollback;

## 可以观察事务2 事务正常提交

# 6. 设置数据库自动提交
set autocommit = 1;

乐观锁

乐观锁大部分实现基于版本机制(Version 或者 Timestamp)来实现, 相对悲观锁更加宽松, 大多数情况下, 并发比悲观锁高, 所以开发实际运用更趋近于乐观锁.

实现乐观锁

# 1. 更新数据库, 新增version 字段
ALTER table test add version int not null;

# 2. 设置数据库不自动提交
set autocommit = 0;

# 3. 事务1 执行
select * from test where id = 1 ; 

## 可以观察事务2 version 字段为 0

# 4. 事务2 执行
select * from test where id = 1;

## 可以观察事务2 version 字段为 0

# 5. 事务1 执行
update test set age = 24 , version = version+1 where id = 1 and version = 0;
commit;

## 可以观察事务1 事务正常提交

# 6. 事务2 执行
update test set age = 24 , version = version+1 where id = 1 and version = 0;
commit;

## 可以观察事务2 正常提交, 但是受影响的行数为 0.

# 7. 设置数据库自动提交
set autocommit = 1;
commit;

乐观锁使用建议

乐观锁在使用的时候, 不一定在同一事务中, 只是检测version字段更新时是否和读取时值保持一致, 若一致则更新, 否则更新失败. 现在很多orm 都有实现version自动更新的功能, 使用非常方便, 也推荐使用该方式处理并发.

你可能感兴趣的:(MySQL)