MySQL学习笔记(四)悲观锁 for update

832 阅读5分钟
原文链接: www.cnblogs.com

恼骚

最近在搞并发的问题,订单的异步通知和主动查询会存在并发的问题,用到了Mysql数据库的 for update 锁

在TP5直接通过lock(true),用于数据库的锁机制

?
1 Db::name('pay_order')->where('order_no',‘S1807081342018949’)->lock(true)->find();

 打印生成的SQL语句

?
1 SELECT * FROM `pay_order` WHERE  `order_no` = 'S1807081342018949' LIMIT 1   FOR UPDATE

for update 是什么? 

以下这句话应用来自:www.cnblogs.com/bigfish--/a…

在oracle中,利用 select * for update 可以锁表。假设有个表单products ,里面有id跟name二个栏位,id是主键。 

例1: (明确指定主键,并且有此笔资料,row lock) 

?
1 SELECT * FROM products WHERE id='3' FOR UPDATE;

例2: (明确指定主键,若查无此笔资料,无lock) 

?
1 SELECT * FROM products WHERE id='-1' FOR UPDATE;

例3: (无主键,table lock) 

?
1 SELECT * FROM products WHERE name='Mouse' FOR UPDATE ;

例4: (主键不明确,table lock) 

?
1 SELECT * FROM products WHERE id<>'3' FOR UPDATE;

例5: (主键不明确,table lock) 

?
1 SELECT * FROM products WHERE id LIKE '3' FOR UPDATE ;

注1: FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。

注2: 要测试锁定的状况,可以利用MySQL的Command Mode ,开二个视窗来做测试。(点开链接,这里已经有人做个测试了) 

先开始一把

使用悲观锁的原理就是,当我们在查询出 pay_order 信息后就把当前的数据锁定,直到我们修改完毕后再解锁。那么在这个过程中,因为 pay_order 被锁定了,就不会出现其他操作者来对其进行修改了。 

第一次,开启事务,但是不提交事务

异步通知

?
1 2 3 4 5 6 7 8 9 -- 开启事务 START TRANSACTION; -- 查询订单 SELECT id,order_no,`status` FROM `pay_order` WHERE  `order_no` = 'S1807081342018949' LIMIT 1   FOR UPDATE; -- 修改订单 UPDATE `pay_order` SET `status` = 11 WHERE id = 347; COMMIT; -- 查询数据是否修改成功 SELECT id,order_no,`status` FROM `pay_order` WHERE  `order_no` = 'S1807081342018949' LIMIT 1   FOR UPDATE;

执行结果:很快就执行完毕了,但是数据并没有修改成功(注意:但是重复执行一次,则数据又修改成功了)

主动查询 

1、加锁

?
1 SELECT id,order_no,`status` FROM `pay_order` WHERE  `order_no` = 'S1807081342018949' LIMIT 1   FOR UPDATE;

 执行结果,一直在阻塞中

过一会,会自动取消锁机制

?
1 [Err] 1205 - Lock wait timeout exceeded; try restarting transaction

2、不加锁

?
1 SELECT id,order_no,`status` FROM `pay_order` WHERE  `order_no` = 'S1807081342018949';

 执行结果,没有阻塞,则能正常查询出数据,不会受第一个事务的影响

第二次,开启事务,提交事务

异步查询开启事务,提交事务

主动查询加锁则不受影响

总结:锁如果是回滚或者提交事务,会自动释放掉锁的。

 

下面研究以下行锁和表锁

 例1: 明确指定主键,并且有此数据,row lock

说明:通过上面的演示,可以清楚的看到,锁的是同一个记录(id = 347),而不是同一个记录(id = 348)并没有受到上一条记录的影响。  

 

例2: 明确指定主键,若查无此数据,无lock

  

 说明:窗口1 查询结果为空。窗口2 查询结果也为空,查询无阻塞,说明 窗口1 没有对数据执行锁定。

 

 例3:无主键,table lock

说明:

窗口1 开启了事务,查询订单号 : order_no = "S1807081342018949",查询数据正常。

窗口2 也开启了事务,查询订单号 : order_no = "S1807081342018949",查询阻塞,说明 窗口1 把该记录给锁住了(其实这里表已经被锁定, 而不是该记录了)。

窗口3 开启了事务,查询订单号 : order_no = "S1807171712053133",查询阻塞,说明 窗口1 把该表给锁住了,不是同一条记录都不给查啊,阻塞的不要不要的。

只有 窗口1 的记录回滚或者提交了,窗口2 的查询阻塞立刻释放掉了,但是 窗口3 依然在阻塞中(由于 窗口2 开启了事务导致的)。同理,回滚或者提交 窗口2 的事务后,窗口3 的记录也可以正常查询了。

 

例4: 主键不明确,table lock

说明:

窗口1 开启了事务,查询主键 : id > 375 的记录,查询数据正常(3条记录)。

窗口2 也开启了事务,查询订单号 :  id > 375 的记录,查询阻塞,说明 窗口1 把该记录给锁住了(其实这里表已经被锁定, 而不是该记录了)。

窗口3 开启了事务,查询订单号 : id > 376 的记录,查询阻塞,说明 窗口1 把该表给锁住了,不是同一条记录都不给查啊,阻塞的不要不要的。

只有 窗口1 的记录提交事务了,窗口2 的查询阻塞立刻释放掉了,窗口3 的记录也可以正常查询了。

 

例5: 主键不明确,table lock

?
1 select * from pay_order where id<>1 for update;

  

索引对数据库的锁定级别

例6: 明确指定索引,并且有此数据,row lock

?
1 2 3 4 5 6 7 8 9 mysql> select id,status,order_no from pay_order where status=1 for update; +------+----------+-------------------+ |   id |   status | order_no          | |------+----------+-------------------| |  348 |        1 | S1807081353042055 | |  349 |        1 | S1807081356043257 | +------+----------+-------------------+ 13 rows in set Time: 0.003s

注意:上面的字段 status 是建立过索引的  

 

例7: 明确指定索引,若查无此数据,无lock

?
1 2 3 4 5 6 7 mysql> select id,status,order_no from pay_order where status=11 for update; +------+----------+------------+ | id   | status   | order_no   | |------+----------+------------| +------+----------+------------+ 0 rows in set Time: 0.001s

  

贵在坚持,相信自己.