Mysql锁机制--乐观锁 & 悲观锁

时间:2021-11-20 06:58:48

前言

mysql的并发操作时而引起的数据的不一致性(数据冲突):

丢失更新:两个用户(或以上)对同一个数据对象操作引起的数据丢失。

    解决方案:1.悲观锁,假设丢失更新一定存在;sql后面加上for update;这是数据库的一种机制。

         2.乐观锁,假设丢失更新不一定发生。update时候存在版本,更新时候按版本号进行更新。

第一部分 悲观锁

1 概念

悲观锁,正如其名,它指的是对数据被外界(包括当前系统的其它事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排它性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

2 命令行演示

2.1 准备数据

Mysql锁机制--乐观锁 & 悲观锁
DROP DATABASE IF EXISTS cyhTest;
CREATE DATABASE cyhTest;

USE cyhTest;

DROP TABLE IF EXISTS employee;

CREATE TABLE IF NOT EXISTS employee (
  id      INTEGER NOT NULL,
  money   INTEGER,
  version INTEGER,
  PRIMARY KEY (id)
)
  ENGINE = INNODB;

INSERT INTO employee VALUE (1, 0, 1);

SELECT * FROM employee;
Mysql锁机制--乐观锁 & 悲观锁

Mysql锁机制--乐观锁 & 悲观锁

目前数据库中只有一条记录,且初始Money=0

2.2 测试

测试准备:

  • 还是两个会话(终端),左边会话是白色背景、右边会话是黑色背景
  • 关闭自动提交:set autocommit = 0; 

现在开始测试:

第一步:两个终端均关闭自动提交

左边:

Mysql锁机制--乐观锁 & 悲观锁

右边:

Mysql锁机制--乐观锁 & 悲观锁

第二步:左边利用 select .... for update 的悲观锁语法锁住记录

select * from employee where id = 1 for update; 

Mysql锁机制--乐观锁 & 悲观锁

第三步:右边也尝试利用 select .... for update 的悲观锁语法锁住记录

Mysql锁机制--乐观锁 & 悲观锁

可以看到,Sql语句被挂起(被阻塞)!

提示:如果被阻塞的时间太长,会提示如下:

Mysql锁机制--乐观锁 & 悲观锁

第四步:左边执行更新操作并提交事务

Sql语句:

update employee set money = 0   1 where id = 1;
commit; 

结果:

Mysql锁机制--乐观锁 & 悲观锁

分析:

  • Money 的旧值为0,所以更新时 Money=0 1
  • 一执行 commit 后,注意查看右边Sql语句的变化

第五步:查看右边Sql语句的变化

Mysql锁机制--乐观锁 & 悲观锁

分析:

  • 被左边悲观锁阻塞了 11.33 秒
  • Money=1,这是左边更新后的结果

2.3 结论

可以看到,当左边(事务A)使用了 select ... for update 的悲观锁后,右边(事务B)再想使用将被阻塞,同时,阻塞被解除后事务B能看到事务A对数据的修改,所以,这就可以很好地解决并发事务的更新丢失问题啦(诚然,这也是人家悲观锁的分内事)

第二部分 乐观锁

1 概念

1.1 理解方式一(来自网上其它小伙伴的博客)

乐观锁认为一般情况下数据不会造成冲突,所以在数据进行提交更新时才会对数据的冲突与否进行检测。如果没有冲突那就OK;如果出现冲突了,则返回错误信息并让用户决定如何去做。

1.2 理解方式二(来自网上其它小伙伴的博客)

乐观锁的特点是先进行业务操作,不到万不得已不会去拿锁。乐观地认为拿锁多半会是成功的,因此在完成业务操作需要实际更新数据的最后一步再去拿一下锁。

1.3 我的理解

理解一:就是 CAS 操作

理解二:类似于 SVN、GIt 这些版本管理系统,当修改了某个文件需要提交的时候,它会检查文件的当前版本是否与服务器上的一致,如果一致那就可以直接提交,如果不一致,那就必须先更新服务器上的最新代码然后再提交(也就是先将这个文件的版本更新成和服务器一样的版本)

乐观锁不是数据库自带的,需要我们自己去实现。

2 如何实现乐观锁呢

首先说明一点的是:乐观锁在数据库上的实现完全是逻辑的,数据库本身不提供支持,而是需要开发者自己来实现。

常见的做法有两种:版本号控制及时间戳控制。

版本号控制的原理:

  • 为表中加一个 version 字段;
  • 当读取数据时,连同这个 version 字段一起读出;
  • 数据每更新一次就将此值加一;
  • 当提交更新时,判断数据库表中对应记录的当前版本号是否与之前取出来的版本号一致,如果一致则可以直接更新,如果不一致则表示是过期数据需要重试或者做其它操作(PS:这完完全全就是 CAS 的实现逻辑呀~)

至于时间戳控制,其原理和版本号控制差不多,也是在表中添加一个 timestamp 的时间戳字段,然后提交更新时判断数据库中对应记录的当前时间戳是否与之前取出来的时间戳一致,一致就更新,不一致就重试。

特点

乐观并发控制相信事务之间的数据竞争概率是较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁