mysql的乐观锁,悲观锁(共享锁,排斥锁)
mysql的并发操作时而引起的数据的不一致性(数据冲突):
丢失更新:两个用户(或以上)对同一个数据对象操作引起的数据丢失。
解决方案:1.悲观锁,假设丢失更新一定存在;sql后面加上**for update;**这是数据库的一种机制。
2.乐观锁,假设丢失更新不一定发生。update时候存在版本,更新时候按版本号进行更新。
一、乐观锁
乐观锁 不是数据库自带 的,需要我们自己去实现。 乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
通常实现是这样的:在表中的数据进行操作时(更新), 先给数据表加一个版本(version)字段 ,每操作一次,将那条记录的版本号加1。也就是 先查询出那条记录 ,获取 出version字段 ,如果要对那条记录进行操作(更新),则 先判断此刻version的值是否与刚刚查询出来时的version的值相等 ,如果相等,则说明这段期间,没有其他程序对其进行操作, 则可以执行更新,将version字段的值加1 ;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。
下单操作包括3步骤:
1.查询出商品信息
select (status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
二、悲观锁
与乐观锁相对应的就是悲观锁了。 悲观锁就是在 操作数据时,认为此操作会出现数据冲突 ,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作 , 这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。
共享锁
共享锁指的就是**对于多个不同的事务,对同一个资源共享同一个锁。**相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,进去啪啪啪啥的,一下理解了哈,没错,这个就是所谓的共享锁。
共享锁也属于悲观锁的一种,那么共享锁在mysql中是通过什么命令来调用呢。通过查询资料,了解到通过在执行语句后面加上 lock in share mode 就代表对某些资源加上共享锁了。
比如,我这里通过mysql打开两个查询编辑器,在其中开启一个事务, 并不执行commit语句
city表DDL如下:
CREATE TABLE `city` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`state` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
begin;
SELECT * from city where id = "1" lock in share mode;(不提交,commit)
然后在另一个查询窗口中,对id为1的数据进行更新
update city set name=“666” where id =“1”; (未commit)
执行数秒,报错
此时,操作界面进入了卡顿状态,过几秒后,也提示错误信息
[SQL]update city set name="666" where id ="1";
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
那么证明,对于id=1的记录加锁成功了,在 上一条记录还没有commit之前 ,这条id=1的记录被锁住了,只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。
再实验一下:
update city set name="666" where id ="1" lock in share mode;
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'lock in share mode' at line 1
加上共享锁后 ,也提示错误信息了,通过查询资料才知道,对于update,insert,delete语句会自动加排它锁的原因
于是,我又试了试SELECT * from city where id = “1” lock in share mode;(没问题)
排它锁
排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。
与共享锁类型,在需要执行的语句后面加上for update就可以了
(对于 Innodb引擎 语句后面加上for update表示 把此行数据锁定 , MyISAM则是锁定整个表 。)
InnoDB中的select … for update语句:
1)select .. for update语句仅适用于InnoDB
2)select .. for update语句必须在事务中才能生效。
3)在执行事务中的select .. for update语句时,MySQL会对查询结果集中的每行数据都添加排他锁(行锁、表锁),其它线程对锁定行的 更新、删除、select .. for update查询 这3种操作都会被阻塞,一般的select语句不会被阻塞。
4)查看自动提交是否开启(1表示开启,0表示关闭,默认开启): select @@autocommit
5)InnoDB行级锁的实现:InnoDB的行级锁是通过在索引上加锁来实现的,所以只有通过明确的索引来查找数据时才会使用行级锁。
MySQL InnoDB为例
商品goods表中有一个字段status,status为1代表商品未被下单,status为2代表商品已经被下单,那么我们对某个商品下单时必须确保该商品status为1。假设商品的id为1。
1如果不采用锁,那么操作方法如下:
//1.查询出商品信息
select status from t_goods where id=1;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
上面这种场景在高并发访问的情况下很可能会出现问题。
前面已经提到,只有当goods status为1时才能对该商品下单,上面第一步操作中,查询出来的商品status为1。但是当我们执行第三步Update操作的时候, 有可能出现其他人先一步对商品下单把goods status修改为2了 ,但是我们并不知道数据已经被修改了,这样就可能造成同一个商品被下单2次,使得数据不一致。所以说这种方式是不安全的。
2使用悲观锁来实现:
在上面的场景中,商品信息从查询出来到修改,中间有一个处理订单的过程,使用悲观锁的原理就是,当我们在查询出goods信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为goods被锁定了,就不会出现有第三者来对其进行修改了。
注:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。
我们可以使用命令设置MySQL为非autocommit模式:
set autocommit=0;
设置完autocommit后,我们就可以执行我们的正常业务了。具体如下:
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;
注:上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交,在这里就不细表了。
上面的第一步我们执行了一次查询操作:
select status from t_goods where id=1 for update;
与普通查询不一样的是,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。
注:需要注意的是,在事务中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响 。拿上面的实例来说,当我执行select status from t_goods where id=1 for update;后。我在另外的事务中如果再次执行select status from t_goods where id=1 for update;则第二个事务会一直等待第一个事务的提交,此时第二个查询处于阻塞的状态,但是如果我是在第二个事务中执行select status from t_goods where id=1;则能正常查询出数据,不会受第一个事务的影响。
补充:MySQL select…for update的Row Lock与Table Lock
MyISAM只支持表锁。
上面我们提到,使用select…for update会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
例:
假設有個表單 products ,裡面有 id 跟 name 二個欄位,id 是主鍵。
例1: (明確指定主鍵,並且有此筆資料,row lock)
SELECT * FROM products WHERE id='3' FOR UPDATE;
例2: (明確指定主鍵,若查無此筆資料,無 lock)
SELECT * FROM products WHERE id='-1' FOR UPDATE;
例3: (無主鍵,table lock)
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;
例4: (主鍵不明確,table lock)
SELECT * FROM products WHERE id<>'3' FOR UPDATE; !=与<> ,意思一样
例5: (主鍵不明確,table lock)
SELECT * FROM products WHERE id LIKE '3' FOR UPDATE;
另外的:
排它锁的选择:若where条件中明确指定了主键,且该行数据存在,则只锁定该行,故排它锁为行锁(row lock)。若where条件中明确指定了主键,但是该行数据不存在,则不会加锁。若where条件中明确指定了索引,且该行数据存在,则只锁定该行,故排它锁为行锁(row lock)。若where条件中明确指定了索引,但是该行数据不存在,则不会加锁。若where条件中未明确指定主键或索引,则会锁定全表,故排它锁为表锁(table lock)。注:未明确指定 即 未指定(主键/索引) 或 指定的是(主键/索引)的范围
eg:# 只锁定message_id为1的行set autocommit=0;begin;select * from t_message where message_id=1 for update; # message_id为主键commit;# 锁定全表set autocommit=0;begin;select * from t_message where message_id>1 for update; # message_id为主键commit;# 锁定全表set autocommit=0;begin;select * from t_message where type='good' for update; # good非索引列commit;其它线程因为等待(排它锁)超时而报错:update t_message set title='asdf' where message_id=1;[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
释放锁
1、暴力解决方式
重启MYSQL(重启解决问题利器,手动滑稽)
2、查看表情况:
show processlist;
State状态为Locked即被其他查询锁住
3、kill掉锁表的进程ID
KILL 10866;//后面的数字即时进程的ID`
寄语:
谢谢支持!
mysql的乐观锁,悲观锁(共享锁,排斥锁)相关推荐
- MySQL:锁机制 |表级锁、行级锁 | 排它锁、共享锁 | 间隙锁
文章目录 表级锁&行级锁 排它锁 & 共享锁 InnoDB行级锁 间隙锁--解决幻读 表级锁&行级锁 表级锁:对整张表加锁.开销小,加锁快,不会出现死锁:锁粒度大,发生锁冲突的 ...
- mysql记录锁与互斥锁区别_MySQL的各种锁认知
一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁) |--悲观锁( ...
- mysql 悲观锁 共享锁_MySQL 乐观锁 悲观锁 共享锁 排他锁
乐观锁 乐观锁是逻辑概念上的锁,不是数据库自带的,需要我们自己去实现.乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁 ...
- mysql锁机制——乐观锁、悲观锁;共享锁、排他锁、行表锁、间隔后码锁、MVCC 与 thinkphp的lock解析
锁的引入 如果A有100元,同时对B.C转账,若处理是同时的,则此时同时读取A的余额为100元,在对两人转账后写回,A的余额不是0元而是50元.因此,为了防止这种现象的出现,要引入锁的概念,如只有在A ...
- mysql锁机制(共享锁(S)、排他锁(X)、意向共享锁(IS)、意向排他锁(IX)的关系,死锁,乐观锁,悲观锁...)
1. 锁的基础与行锁的特点 1.1 概念 在开发多用户.数据库驱动的应用时,相当大的一个难点就是解决并发性的问题,目前比较常用的解决方案就是锁机制. 锁机制也是数据库系统区别于文件系统的一个关键特性. ...
- java 对变量加锁_Java最全锁剖析:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁...
乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度,在Java和数据库中都有此概念对应的实际应用. 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会 ...
- mysql隔离级别与悲观锁、乐观锁
2019独角兽企业重金招聘Python工程师标准>>> 1.什么是悲观锁,乐观锁 悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性.但随之而来的就是数据库性能的大 ...
- Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”
在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...
- laravel mysql 锁表_Laravel中MySQL的乐观锁与悲观锁
MySQL/InnoDB的加锁,是一个老生常谈的话题.在数据库高并发请求下,如何兼顾数据完整性与用户体验的敏捷性是一代又一代程序员一直在思考的问题. 乐观锁 乐观锁之所以叫乐观,是因为这个模式不会对数 ...
最新文章
- WGCNA加权基因共表达网络分析(1)简介、原理
- 淘宝开源的代码质量检测工具!
- vivado2017.4安装教程
- Vue3、TypeScript 实现图片数量及大小随宽度自适应调整
- Opencv ---像素坐标转世界坐标(已知外参)
- sublime mysql插件_开发者最常用的8款Sublime text 3插件
- 2.Cannot find config.m4.
- cron 每10分钟执行一次_早餐儿子最爱它,简单卷一卷,10分钟做一大盘,三天两头吃一次...
- linux中常用安装程序的方法,Linux下常见的软件安装方式
- 关于TCP协议的大部分,面试常问的几点都在这理里了
- 后端开发如何设计数据库系列文章(二)设计大数据量表结构
- Java非静态内部类中为什么不能有静态变量却可以有常量?
- 计算机维护记录软件,《企业计算机软件故障维护记录》(4页)-原创力文档
- Anaconda下载速度慢
- 超简单制作栅格数据土地利用转移矩阵(Arcgis和Python实现)
- macd的VB计算机程序,大智慧自选股实时同步到通达信VB小软件简单升级
- 【设计模式从青铜到王者】第八篇:创建型模式之建造者模式(BuilderPattern)
- SpringBoot整合ThymeLeaf前后端分离使用案例
- 干货 | 奇秀直播连麦技术探索
- 题十:二叉搜索树与双向链表