恼骚

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

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

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

打印生成的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语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。

理解悲观锁与乐观锁

在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和一致性以及数据库的一致性。

乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。无论是悲观锁还是乐观锁,都是人们定义出来的概念,可以认为是一种思想。其实不仅仅是数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。

针对于不同的业务场景,应该选用不同的并发控制方式。所以,不要把乐观并发控制和悲观并发控制狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。其实,在DBMS中,悲观锁正是利用数据库本身提供的锁机制来实现的。

在数据库中,悲观锁的流程如下:

在对任意记录进行修改前,先尝试为该记录加上排他锁(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;

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

主动查询 

1、加锁

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

2、不加锁

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 依然在阻塞中(由于 窗口2 开启了事务导致的)。同理,回滚或者提交 窗口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

演示操作

1、定义索引字段开启事务加悲观锁

pay_order 表结构,order_no 定义索引。

CREATE TABLE `pay_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '#',
`order_no` varchar(255) NOT NULL COMMENT '订单号',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `idx_order_no` (`order_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=514 DEFAULT CHARSET=utf8mb4 COMMENT='支付订单';

相同订单号第一次查询

mysql@:Tinywan> START TRANSACTION;
Query OK, 0 rows affected
Time: 0.000s
mysql@:Tinywan> SELECT id,order_no,`status` FROM `pay_order` WHERE order_no = 'S64064191 -> 1161202555241' LIMIT 1 FOR UPDATE;
+------+------------------------+----------+
| id | order_no | status |
|------+------------------------+----------|
| 11 | S640641911161202555241 | 1 |
+------+------------------------+----------+
1 row in set
Time: 0.001s
mysql@:Tinywan>

相同订单号第二次查询

mysql@:Tinywan> START TRANSACTION;
Query OK, 0 rows affected
Time: 0.001s
mysql@:Tinywan> SELECT id,order_no,`status` FROM `pay_order` WHERE order_no = 'S640641911161 -> 202555241' LIMIT 1 FOR UPDATE;

被阻塞掉了

不同订单号第一次查询

mysql@:Tinywan> START TRANSACTION;
Query OK, 0 rows affected
Time: 0.000s
mysql@:Tinywan> SELECT id,order_no,`status` FROM `pay_order` WHERE
-> order_no = 'T705961911161238428844' LIMIT 1 FOR UP -> DATE;
+------+------------------------+----------+
| id | order_no | status |
|------+------------------------+----------|
| 25 | T705961911161238428844 | 0 |
+------+------------------------+----------+
1 row in set
Time: 0.002s
mysql@:Tinywan>

2、未定义索引

表结构

CREATE TABLE `pay_order` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '#',
`order_no` varchar(255) NOT NULL COMMENT '订单号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=514 DEFAULT CHARSET=utf8mb4 COMMENT='支付订单';

相同订单号第一次查询

相同订单号第二次查询

不同订单号第一次查询

第一个提交事务

第二个提交事务

参考:

1、理解悲观锁与乐观锁

MySQL学习笔记(四)悲观锁与乐观锁的更多相关文章

  1. MySQL学习(四)深入理解乐观锁与悲观锁

    转载自:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据 ...

  2. MySql学习笔记四

    MySql学习笔记四 5.3.数据类型 数值型 整型 小数 定点数 浮点数 字符型 较短的文本:char, varchar 较长的文本:text, blob(较长的二进制数据) 日期型 原则:所选择类 ...

  3. MySQL学习笔记(四):存储引擎的选择

    一:几种常用存储引擎汇总表 二:如何选择 一句话:除非需要InnoDB 不具备的特性,并且没有其他办法替代,否则都应该优先考虑InnoDB:或者,不需要InnoDB的特性,并且其他的引擎更加合适当前情 ...

  4. MySQL学习笔记四:字符集

    1.字符集就是字符和其编码的集合,查看数据库支持的字符集 show character set 2.查看服务端启动时默认的字符集 mysql> show variables like 'char ...

  5. MySQL学习笔记(四)—存储过程

    一.概述      存储过程是数据库定义的一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程可以避免开发人员重复的编写相同的SQL语句,而且存储过程是在MySq ...

  6. mysql学习笔记四 —— AB复制

    要点:ab复制 mysql集群架构流程: ABB(主从复制)-->MHA(实现mysql高可用.读写分离.脚本控制vip飘逸)-->haproxy(对slave集群实现分发,负载均衡)-- ...

  7. MySql学习笔记(四) —— 数据的分组

    前面介绍的聚集函数只是用来计算行数,平均数,最大值,最小值而不用检索所有数据.通过count()函数,我们可以计算生产商1003提供的产品数目,但如果我要查询所有生产商提供的商品数,这就需要进行分组查 ...

  8. 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

  9. 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景

    在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...

随机推荐

  1. SQLServer之索引简介

    索引设计基础知识 索引是与表或视图关联的磁盘上结构,可以加快从表或视图中检索行的速度. 索引包含由表或视图中的一列或多列生成的键. 这些键存储在一个结构(B 树)中,使 SQL Server 可以快速 ...

  2. flink如何动态支持依赖jar包提交

    通常我们在编写一个flink的作业的时候,肯定会有依赖的jar包.flink官方希望你将所有的依赖和业务逻辑打成一个fat jar,这样方便提交,因为flink认为你应该对自己的业务逻辑做好单元测试, ...

  3. CentOS 7 中使用NTP进行时间同步

    1. NTP时钟同步方式说明NTP在linux下有两种时钟同步方式,分别为直接同步和平滑同步: 直接同步 使用ntpdate命令进行同步,直接进行时间变更.如果服务器上存在一个12点运行的任务,当前服 ...

  4. javaScript判断手机型号

    window.onload = function () { alert("1"); var u = navigator.userAgent; if (u.indexOf('Andr ...

  5. 浅析String类

    这是对于String类的一些总结,我将会从几个方面并且结合着字符串池等相关知识进行总结 进程如下:                1.对于String类基本知识的一些总结 2.简要介绍字符串池 3.分 ...

  6. (golang)HTTP基本认证机制及使用gocolly登录爬取

    内网有个网页用了HTTP基本认证机制,想用gocolly爬取,不知道怎么登录,只好研究HTTP基本认证机制 参考这里:https://www.jb51.net/article/89070.htm 下面 ...

  7. ESP8266远程OTA升级

    https://blog.csdn.net/xh870189248/article/details/80095139 https://www.wandianshenme.com/play/arduin ...

  8. 软工+C(2): 分数和checklist

    // 上一篇:题目设计.点评和评分 // 下一篇:超链接 教学里,建立清晰明确的评分规则并且一开始就公布,对于教师.助教.学生都是重要的. 公布时机 在课程开始的时候,就需要确定并公布评分机制,随着课 ...

  9. sql 日常使用记录

    sql 某个字段在哪些表中存在: select sysobjects.name from syscolumns inner join sysobjects on syscolumns.id = sys ...

  10. Ajax提交表单初接触

    <!doctype html> <html class="no-js"> <head> <meta charset="utf-8 ...