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;
img
begin;
SELECT * from city where id = "1"  lock in share mode;(不提交,commit)

然后在另一个查询窗口中,对id为1的数据进行更新

img

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的记录被锁住了,只有在上一个事务释放掉锁后才能进行操作,或用共享锁才能对此数据进行操作。

再实验一下:

img
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`

寄语:在这里插入图片描述
谢谢支持!在这里插入图片描述

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会敲代码的泡椒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值