恼骚

最近在搞并发的问题,订单的异步通知和主动查询会存在并发的问题,用到了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

上面的查询语句中,我们使用了 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)

  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. START TRANSACTION;
  3. -- 查询订单
  4. SELECT id,order_no,`status` FROM `pay_order` WHERE `order_no` = 'S1807081342018949' LIMIT 1 FOR UPDATE;
  5. -- 修改订单
  6. UPDATE `pay_order` SET `status` = 11 WHERE id = 347;
  7. COMMIT;
  8. -- 查询数据是否修改成功
  9. 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 依然在阻塞中(由于 窗口2 开启了事务导致的)。同理,回滚或者提交 窗口2 的事务后,窗口3 的记录也可以正常查询了。

例5: 主键不明确,table lock

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

索引对数据库的锁定级别

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

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

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

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

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

演示操作

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

pay_order 表结构,order_no 定义索引。

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

相同订单号第一次查询

  1. mysql@:Tinywan> START TRANSACTION;
  2. Query OK, 0 rows affected
  3. Time: 0.000s
  4. mysql@:Tinywan> SELECT id,order_no,`status` FROM `pay_order` WHERE order_no = 'S64064191 -> 1161202555241' LIMIT 1 FOR UPDATE;
  5. +------+------------------------+----------+
  6. | id | order_no | status |
  7. |------+------------------------+----------|
  8. | 11 | S640641911161202555241 | 1 |
  9. +------+------------------------+----------+
  10. 1 row in set
  11. Time: 0.001s
  12. mysql@:Tinywan>

相同订单号第二次查询

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

被阻塞掉了

不同订单号第一次查询

  1. mysql@:Tinywan> START TRANSACTION;
  2. Query OK, 0 rows affected
  3. Time: 0.000s
  4. mysql@:Tinywan> SELECT id,order_no,`status` FROM `pay_order` WHERE
  5. -> order_no = 'T705961911161238428844' LIMIT 1 FOR UP -> DATE;
  6. +------+------------------------+----------+
  7. | id | order_no | status |
  8. |------+------------------------+----------|
  9. | 25 | T705961911161238428844 | 0 |
  10. +------+------------------------+----------+
  11. 1 row in set
  12. Time: 0.002s
  13. mysql@:Tinywan>

2、未定义索引

表结构

  1. CREATE TABLE `pay_order` (
  2. `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '#',
  3. `order_no` varchar(255) NOT NULL COMMENT '订单号',
  4. PRIMARY KEY (`id`) USING BTREE
  5. ) 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. Arch Linux安装Firefox 火狐中文版

    很多人刚安装好系统之后,刚开始内置的浏览器是火狐的英文版,很多时候因为需要账号同步的原因需要国内版本的火狐浏览器,这个时候我们应该怎么操作呢? 其实也非常的简单 首先我们 输入命令 pacman -S ...

  2. MySql 学习之路-高级2

    目录: 1.约束 2.ALTER TABLE 3.VIEW 1.约束 说明:SQL约束用于规定表中的数据规则,如果存在违反约束的数据行为,行为会被约束终止,约束可以在建表是规定,也可以在建表后规定,通 ...

  3. HTML之间互相传参

    如图所示,在index.html详情展示中给detailsPanel穿参数,在detailsPanel中获取到参数写ajax到后台获取json数据,那么如何在detailsPanel.html中获取传 ...

  4. 重写override

    不可重写私有方法. 不可重写非静态的方法,虽然编译器不会报错,但是得不到预期的结果. 可以通过重写的形式对父类的功能进行重新定义,比如:对功能进行修改或者进行升级时. class BaseAction ...

  5. consul 搭建

    windows 1. 下载consul https://www.consul.io/downloads.html 2. 解压至consul_1.4.2 3.配置环境变量 path下新增D:\work\ ...

  6. SQL CREATE TABLE 语句

    CREATE TABLE 语句 CREATE TABLE 语句用于创建数据库中的表. SQL CREATE TABLE 语法 CREATE TABLE 表名称 ( 列名称1 数据类型, 列名称2 数据 ...

  7. os模块使用

    Python获取当前文件名的两种方法 1,使用python文件默认的‘ file ’属性 2,使用 sys.argv[0] print sys.argv # 输入参数列表print sys.argv[ ...

  8. pybind11 安装

    Prerequisites: $ sudo apt-get install python-dev  (or python3-dev) $ sudo apt-get install cmake $ su ...

  9. 修改xampp-apache访问目录

    文章转自 https://my.oschina.net/u/3618644/blog/1569972 问题来源: 一般情况下,每个项目占用一个根目录,而不是一个根目录下面有多个项目. 比如说,安装xa ...

  10. 初学python必备基础知识

    一,编程语言介绍 1.机器语言:直接用二进制编程,直接控制硬件,需要掌握硬件的操作细节 优点:执行效率高   缺点:   开发效率低 2.汇编语言:用英文标签取代二进制指令去编写程序,直接控制硬件,需 ...