老司机带大家领略MySQL中的乐观锁和悲观锁
原文地址:https://cloud.tencent.com/developer/news/227982
为什么需要锁
在并发环境下,如果多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解决,通过加锁的机制,常见的有两种锁,乐观锁和悲观锁,可以在一定程度上解决并发访问。
乐观锁
乐观锁,顾名思义,对加锁持有一种乐观的态度,即先进行业务操作,不到最后一步不进行加锁,"乐观"的认为加锁一定会成功的,在最后一步更新数据的时候在进行加锁,乐观锁的实现方式一般为每一条数据加一个版本号,具体流程是这样的:
1)、创建一张表时添加一个version字段,表示是版本号,如下:

2)、修改数据的时候首先把这条数据的版本号查出来,update时判断这个版本号是否和数据库里的一致,如果一致则表明这条数据没有被其他用户修改,若不一致则表明这条数据在操作期间被其他客户修改过,此时需要在代码中抛异常或者回滚等。伪代码如下:
updatetbsetname='yyy'andversion=version+1whereid=1andversion=version;
1. SELECT name AS old_name, version AS old_version FROM tb where ...;
2. 根据获取的数据进行业务操作,得到new_dname和new_version
3.UPDATE SET name = new_name, version = new_version WHERE version = old_version
if(updated row >) {
// 乐观锁获取成功,操作完成
}else{
// 乐观锁获取失败,回滚并重试
}
update其实在不在事物中都无所谓,在内部是这样的:update是单线程的,及如果一个线程对一条数据进行update操作,会获得锁,其他线程如果要对同一条数据操作会阻塞,直到这个线程update成功后释放锁。
另外,乐观锁不需要数据库底层的支持!!!
悲观锁
正如其名字一样,悲观锁对数据加锁持有一种悲观的态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
首先我们需要set autocommit=0,即不允许自动提交
用法:select * from tablename where id = 1for update;
申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞。
for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
假设有一张商品表 shop,它包含 id,商品名称,库存量三个字段,表结构如下:

插入如下数据:

并发导致数据一致性的问题:
如果有A、B两个用户需要购买id=1的商品,AB都查询商品数量是1000,A购买后修改商品的数量为999,B购买后修改数量为999。
用乐观锁的解决方案:
每次获取商品时,不对该商品加锁。在更新数据的时候需要比较程序中的库存量与数据库中的库存量是否相等,如果相等则进行更新,反之程序重新获取库存量,再次进行比较,直到两个库存量的数值相等才进行数据更新。伪代码如下:

悲观锁方案:
每次获取商品时,对该商品加排他锁。也就是在用户A获取获取 id=1 的商品信息时对该行记录加锁,期间其他用户阻塞等待访问该记录。悲观锁适合写入频繁的场景。代码如下:
我们可以看到,首先通过begin开启一个事物,在获得shop信息和修改数据的整个过程中都对数据加锁,保证了数据的一致性。
行锁与表锁
下面开启两个客户端模拟两个用户同时竞争数据。
1、只根据主键进行查询,并且查询到数据,主键字段产生行锁。
可以看到:id是主键,当在client1上查询id=1的数据时候,在client2上查询id=2的数据没问题,但在client2上查询id=1的数据时阻塞,说明此时的锁时行锁。当client1执行commit时,clinet2查询的id=1的命令立即返回数据。
2、只根据主键进行查询,没有查询到数据,不产生锁。
可以看到:在client1上查询一条不存在的数据时不会产生任何锁。
3、根据主键、非主键含索引(name)进行查询,并且查询到数据,主键字段产生行锁,name字段产生表锁。
如果我们在client1中执行:
查询到数据后,在client2和client3分别执行:
都会阻塞;
4、根据主键、非主键含索引(name)进行查询,没有查询到数据,不产生锁。
我们在name字段创建idxname的索引:

后没有查询到数据,在client2中id和name字段不会产生锁。
5、根据主键、非主键不含索引(name)进行查询,并且查询到数据,如果其他线程按主键字段进行再次查询,则主键字段产生行锁,如果其他线程按非主键不含索引字段进行查询,则非主键不含索引字段产生表锁,如果其他线程按非主键含索引字段进行查询,则非主键含索引字段产生行锁,如果索引值是枚举类型,mysql也会进行表锁,大家仔细理解一下。
这里就不截图了,大家自己试验。。。
6、根据主键、非主键不含索引(name)进行查询,没有查询到数据,不产生锁。
7、根据非主键含索引(name)进行查询,并且查询到数据,name字段产生行锁,将整行锁住,其他条件查询该数据阻塞。
我们在client1用name='prod11'查询到数据后,在client2中name字段会有行锁,prod12可以返回,prod11阻塞。
8、根据非主键含索引(name)进行查询,没有查询到数据,不产生锁。
9、根据非主键不含索引(name)进行查询,并且查询到数据,name字段产生表锁。
删除name字段的索引idxname;
可以看到,client1通过非索引的name字段查询到prod11的数据后,在client2查prod12的数据会阻塞,产生表锁。
10、根据非主键不含索引(name)进行查询,没有查询到数据,name字段产生表锁。
client1并没有查询到数据,client2任然会产生表锁。
11、只根据主键进行查询,查询条件为不等于,并且查询到数据,主键字段产生表锁

client1通过!=进行范围查询且查询到数据,client2产生表锁。
12、只根据主键进行查询,查询条件为不等于,没有查询到数据,主键字段产生表锁。
13、只根据主键进行查询,查询条件为 like,并且查询到数据,主键字段产生表锁。
14、只根据主键进行查询,查询条件为 like,没有查询到数据,主键字段产生表锁。
11—14都是表锁,因为MySQL不可能通过范围查询查询到的每一条数据都加行锁,太消耗性能,还不如锁表呢(个人理解)。
老司机带大家领略MySQL中的乐观锁和悲观锁的更多相关文章
- 老司机带你走进Core Animation
为什么时隔这么久我又回来了呢? 回来圈粉. 开玩笑的,前段时间ipv6被拒啊,超级悲剧的,前后弄了好久,然后需求啊什么的又超多,所以写好的东西也没有时间整理.不过既然我现在回来了,那么这将是一个井喷的 ...
- 老司机带你开飞机 一: mssql on linux 安装指导
通常在本机开发环境中需要搭建所有的服务,还要修改本地的hosts,实在是不胜其烦.如今有了docker,完全不用污染本地环境,且看老司机带你搭建一个asp.net core的开发环境集群.愿你走出虚拟 ...
- Kali Linux来袭~老司机带你进击
Kali是BackTrackLinux完全遵循Debian开发标准彻底的完全重建.全新的目录框架,复查并打包所有工具,我们还为VCS建立了Git树. 本次推荐内容主要介绍Kali-Linux的安装,包 ...
- 老司机带你玩转面试(2):Redis 过期策略以及缓存雪崩、击穿、穿透
前文回顾 建议前一篇文章没看过的同学先看下前面的文章: 「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」 过期策略 Redis 的过期策略都有哪些? 在聊这个问题之前,一定 ...
- 老司机带你玩转面试(3):Redis 高可用之主从模式
前文回顾 建议前面文章没看过的同学先看下前面的文章: 「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」 「老司机带你玩转面试(2):Redis 过期策略以及缓存雪崩.击穿. ...
- 老司机带你玩转面试(4):Redis 高可用之哨兵模式
前文回顾 建议前面文章没看过的同学先看下前面的文章: 「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」 「老司机带你玩转面试(2):Redis 过期策略以及缓存雪崩.击穿. ...
- 老司机带你玩转面试(5):Redis 集群模式 Redis Cluster
前文回顾 建议前面文章没看过的同学先看下前面的文章: 「老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化」 「老司机带你玩转面试(2):Redis 过期策略以及缓存雪崩.击穿. ...
- [转]MySQL中乐观锁、悲观锁(共享锁、排他锁)简介
InnoDB与MyISAM Mysql 在5.5之前默认使用 MyISAM 存储引擎,之后使用 InnoDB. MyISAM 操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高 ...
- mysql中的乐观锁和悲观锁
mysql中的乐观锁和悲观锁的简介以及如何简单运用. 关于mysql中的乐观锁和悲观锁面试的时候被问到的概率还是比较大的. mysql的悲观锁: 其实理解起来非常简单,当数据被外界修改持保守态度,包括 ...
随机推荐
- 第三节.vue.js属性与方法
1. <!DOCTYPE html><html><head><meta charset="UTF-8"><title>I ...
- info命令
info命令也可以查看命令的信息,但是和man命令不同,info命令如同一本书,一个命令只不过是书中的一个章节. 1.基本结构: 输入:info ls ls命令只不过是整个文档的10.1章节. 而在章 ...
- C结构体指针的初步使用
#include <stdio.h> #include <string.h> struct Books { char title[50]; //char author[100] ...
- Cogs 763. [USACO Open09] 数字的游戏(博弈)
[USACO Open09] 数字的游戏 ★☆ 输入文件:cdgame.in 输出文件:cdgame.out 简单对比 时间限制:1 s 内存限制:128 MB Bessie正跟FJ玩一个数字游戏,她 ...
- Ancient Go(简单DFS)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5546 AC代码: #include<iostream> #include<cstdi ...
- gdb tui设置默认窗口高度
gdb -p 12999 -tui 先显示win信息(输入:info win) 显示如下: SRC (35 lines) <has focus> CMD (17 lines) 我们要改的是 ...
- FCS省选模拟赛 Day4
传送门 Solution Code /* 斯坦纳树:O(n*3^n+kE*2^n) 暂且把O(k*E)当成是spfa的复杂度 15:15~16:20 原题:bzoj_4774 */ #include ...
- avalon如何用年月日的方式输出..
在avolon里面的http://avalonjs.coding.me/filter.html 可以找到与date相关的转化,如果是要转化为年月日的形式,看下面的代码: <span style ...
- nginx压力测试和并发预估
一.Nginx并发预估 预估算法:{(?G)*1024-system}/请求大小 (?G):表示内存大小1024:表示内存容量标准进制system:表示系统和服务占用的额外内存和需要预留的内存请求大小 ...
- Ubuntu不能连接网络
我的问题是在选择桥接模式下的界面名称选择错误,在windows中我的Intel7260显示未连接,所以更改为Controller之后好用了,折磨了我大半天.