恼骚php
最近在搞并发的问题,订单的异步通知和主动查询会存在并发的问题,用到了Mysql数据库的 for update 锁html
在TP5直接经过lock(true),用于数据库的锁机制mysql
Db::name('pay_order')->where('order_no',‘S1807081342018949’)->lock(true)->find();
打印生成的SQL语句sql
SELECT * FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE
上面的查询语句中,咱们使用了 select…for update 的方式,这样就经过开启排他锁的方式实现了悲观锁。此时在 pay_order 表中,order_no 为 S1807081342018949 的那条数据就被咱们锁定了,其它的事务必须等本次事务提交以后才能执行。这样咱们能够保证当前的数据不会被其它事务修改。数据库
上面咱们提到,使用 select…for update 会把数据给锁住,不过咱们须要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,若是一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点须要注意。并发
理解悲观锁与乐观锁oracle
在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和一致性以及数据库的一致性。异步
乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。不管是悲观锁仍是乐观锁,都是人们定义出来的概念,能够认为是一种思想。其实不单单是数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有相似的概念。测试
针对于不一样的业务场景,应该选用不一样的并发控制方式。因此,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库自己提供的锁机制来实现的。spa
在数据库中,悲观锁的流程以下:
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
若是加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际须要决定。
若是成功加锁,那么就能够对记录作修改,事务完成后就会解锁了。
其间若是有其余对该记录作修改或加排他锁的操做,都会等待咱们解锁或直接抛出异常。
如下这句话应用来自:http://www.cnblogs.com/bigfish--/archive/2012/02/18/2356886.html
在oracle中,利用 select * for update 能够锁表。假设有个表单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;
注1: FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。
注2: 要测试锁定的情况,能够利用MySQL的Command Mode ,开二个视窗来作测试。(点开连接,这里已经有人作个测试了)
先开始一把
使用悲观锁的原理就是,当咱们在查询出 pay_order 信息后就把当前的数据锁定,直到咱们修改完毕后再解锁。那么在这个过程当中,由于 pay_order 被锁定了,就不会出现其余操做者来对其进行修改了。
第一次,开启事务,可是不提交事务
异步通知
-- 开启事务
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;
执行结果:很快就执行完毕了,可是数据并无修改为功(注意:可是重复执行一次,则数据又修改为功了)
主动查询
一、加锁
SELECT id,order_no,`status` FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE;
执行结果,一直在阻塞中
过一会,会自动取消锁机制
[Err] 1205 - Lock wait timeout exceeded; try restarting transaction
二、不加锁
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
select * from pay_order where id<>1 for update;
索引对数据库的锁定级别
例6: 明确指定索引,而且有此数据,row lock
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
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
参考: