MySql锁机制之乐观锁和悲观锁的验证过程

一.概念

1.悲观锁(Pessimistic Lock)
表示数据对外界系统的影响持悲观保守的态度,其实就是实际意义上的物理锁。
当进行事务操作时,先给当前的数据加锁,直到事务提交。加锁期间(从数据读取开始,修改数据,可能去了趟洗手间,喝了咖啡,电脑锁屏等),数据库记录始终处于加锁状态,其他系统或者其他程序员均无法对当前数据进行修改。
2.悲观锁的实现
往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据),可以看出对于一些长事务来说,当并发量成百上千的时候,系统就很有可能奔溃了。
或者可以简单理解为Java 中的 Synchronized 关键字,关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块。

/**
 * synchronized 修饰实例方法
 */
    public synchronized void increase(){
        i++;
    }

3.乐观锁(Optimistic Lock)
其实本身不存在这种锁,是某种概念上的锁。
相对于悲观锁来说,乐观锁对数据持有乐观的态度,它认为数据处理一般会成功,一般情况下不会造成冲突。特点是从取数据,进行数据操作,期间无论做了什么,耗时多久,都不影响其他系统或者其他人在数据库对当前数据进行操作。
4.乐观锁的实现
通常使用数据库版本Version机制实现
例1.建表时添加一个字段version。数据处理时,把版本号一起提取出来,数据处理完后,version + 1,提交事务时,将要提交的数据与数据库里当前这笔数据对比,若要提交数据的版本号version大于数据库版本号version,则更新数据,否则说明数据库数据已经被他人修改过,此时需要重新提取数据,或者抛出异常。
例2.根据时间戳,比如最后更新时间,同理数据处理时,将最后更新时间与其他字段一起提取出来,提交事务时,若要提交数据的最后更新时间与数据库里当前这笔数据的最后更新时间相同,则更新数据,因为若有人动过数据,则最后更新时间一定会改变。

二.实验

一般来说使用 select … for update,对所选择的数据进行加锁处理,例如select * from stock where name= ‘小米10’ for update, 这条sql语句锁定了stock 表中所有符合检索条件(name= ‘小米10’ )的记录。本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。
1.数据准备

CREATE TABLE stock(
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品id',
`name` varchar(120) NOT NULL COMMENT '商品名称',
`total` int NOT NULL COMMENT '商品数量',
`version` int NOT NULL DEFAULT 0 COMMENT '版本号',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id)
)ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET = utf8 COMMENT = '商品库存表';

可以使用脚本:show tables; 查看建表是否成功,效果如下

show tables; 

在这里插入图片描述
由于建表时id,version,create_time,update_time,使用了默认值,所以插入数据时,只要插入name和total就可以了;

INSERT INTO stock(name,total)
VALUES 
('小米10','100'),
('苹果11','200'),
('华为30','300');

查看数据:

select * from stock;

在这里插入图片描述
2.执行验证
1.同时打开两个终端,并且关闭事务自动提交机制:

set autocommit = 0;

在这里插入图片描述
2.两边同时使用 select … for update; 悲观锁语法锁住数据;
终端1:

select * from stock where id = '1001' for update; 

然后回车,马上返回结果
在这里插入图片描述
终端2:同样输入

select * from stock where id = '1001' for update;

然后回车,会发现此时并没有返回什么结果;
在这里插入图片描述
3.如果等待时间很长,终端2会抛出异常,超过锁定等待超时,尝试重新启动事务;
在这里插入图片描述
报错没事,终端2再次输入

select * from stock where id = '1001' for update; 

在这里插入图片描述
4.此时终端1需要执行更新语句,再手动commit;

update stock set total = 99, version = 1 where id = '1001';

手动提交事务:

commit;

完整操作流程如下,若没有抛出Lock wait timeout exceeded; try restarting transaction异常,则跳过第3步。
在这里插入图片描述
此时,注意一下图中绿色框终端2的操作结果:
终端2只是执行了select * from stock where id = ‘1001’ for update 锁定数据,而返回的结果却是终端1更新后的语句,total由100变成了99,version由0变成了1,update_time也同样更新了;
在这里插入图片描述
还有一个注意的地方,操作时间,意思就是被终端1的事务悲观锁机制阻塞了41.42秒
在这里插入图片描述
5.之后终端2就可以进行自己的事务处理了,比如:

update stock set total = 98, version = 2 where id = '1001';

手动提交后,查看结果
在这里插入图片描述

三.总结

当终端1开始执行事务A时,数据将会被锁定,就像上面的概念一样,此时外界系统或者其他程序员都不能对同一笔数据进行修改,将会发生阻塞现象,只有在终端1提交事务A之后,才能释放锁,此时终端2才能执行其他事务,并且操作的数据就是终端1返回后的数据。

就像Git,SVN等代码版本管理工具,同一个文件的代码,同一时间获取最新代码,你修改你的,我修改我的,修改过程大家互不影响,如果你先提交了,此时后台记录的版本号就已经变更了,此时我再提交代码,就会报冲突了,需要再次获取最新的代码,也就是你操作完之后的代码,然后我才能进行其他操作。

参考博文:
https://www.cnblogs.com/cyhbyw/p/8869855.html

https://www.cnblogs.com/sheseido/p/5038562.html

https://blog.csdn.net/Daybreak1209/article/details/51606939?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

©️2020 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页