MySQL学习笔记(四)悲观锁与乐观锁
恼骚
最近在搞并发的问题,订单的异步通知和主动查询会存在并发的问题,用到了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='支付订单';
相同订单号第一次查询

相同订单号第二次查询

不同订单号第一次查询

第一个提交事务

第二个提交事务

参考:
MySQL学习笔记(四)悲观锁与乐观锁的更多相关文章
- MySQL学习(四)深入理解乐观锁与悲观锁
转载自:http://www.hollischuang.com/archives/934 在数据库的锁机制中介绍过,数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据 ...
- MySql学习笔记四
MySql学习笔记四 5.3.数据类型 数值型 整型 小数 定点数 浮点数 字符型 较短的文本:char, varchar 较长的文本:text, blob(较长的二进制数据) 日期型 原则:所选择类 ...
- MySQL学习笔记(四):存储引擎的选择
一:几种常用存储引擎汇总表 二:如何选择 一句话:除非需要InnoDB 不具备的特性,并且没有其他办法替代,否则都应该优先考虑InnoDB:或者,不需要InnoDB的特性,并且其他的引擎更加合适当前情 ...
- MySQL学习笔记四:字符集
1.字符集就是字符和其编码的集合,查看数据库支持的字符集 show character set 2.查看服务端启动时默认的字符集 mysql> show variables like 'char ...
- MySQL学习笔记(四)—存储过程
一.概述 存储过程是数据库定义的一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程可以避免开发人员重复的编写相同的SQL语句,而且存储过程是在MySq ...
- mysql学习笔记四 —— AB复制
要点:ab复制 mysql集群架构流程: ABB(主从复制)-->MHA(实现mysql高可用.读写分离.脚本控制vip飘逸)-->haproxy(对slave集群实现分发,负载均衡)-- ...
- MySql学习笔记(四) —— 数据的分组
前面介绍的聚集函数只是用来计算行数,平均数,最大值,最小值而不用检索所有数据.通过count()函数,我们可以计算生产商1003提供的产品数目,但如果我要查询所有生产商提供的商品数,这就需要进行分组查 ...
- 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
- 第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景
在日常开发中,尤其是业务开发,少不了利用 Java 对数据库进行基本的增删改查等数据操作,这也是 Java 工程师的必备技能之一.做好数据操作,不仅仅需要对 Java 语言相关框架的掌握,更需要对各种 ...
随机推荐
- OKR相关4本书,好书3本
最近几年看过4本OKR相关的书,有3本是4星.其中第一本是最近看的,剩下3本是2017年看的. OKR源自德鲁克和格鲁夫,跟谷歌是天作之合:4星|<这就是OKR> 4星|<OKR实践 ...
- Skyline 7 版本TerraExplorer Pro二次开发快速入门
年底了,给大家整理了一下Skyline 7版本的二次开发学习初级入门教程,献给那些喜欢学习的年轻朋友. 我这整理的是Web控件版本的开发示例,里面页面代码保存成html,都可以直接运行的. 测试使用的 ...
- FineUI十周年纪念版即将发布(基于像素的响应式布局,独此一家)!
[新版预报]FineUI十周年纪念版(v5.0.0)即将于2018-04-23发布! 官网示例已更新:http://pro.fineui.com/ 特别助攻:基于像素的响应式布局,FineUI独家秘笈 ...
- centOS中mysql一些常用操作
安装mysqlyum -y install mysql-server 修改mysql配置vi /etc/my.cnf 这里会有很多需要注意的配置项,后面会有专门的笔记暂时修改一下编码(添加在密码下 ...
- swipe.js实现支持手拔与自动切换的图片轮播
一.Html代码如下: <div id='mySwipe' style='max-width:500px;margin:0 auto' class='swipe'> <div cla ...
- java 实现递归实现tree
package app.util; import java.util.ArrayList; import java.util.List; import com.alibaba.fastjson.JSO ...
- SQL中ON和WHERE的区别
SQL中ON和WHERE的区别 - 邃蓝星空 - 博客园 https://www.cnblogs.com/guanshan/articles/guan062.html
- rsync用法详细解释
提要 熟悉 rsync 的功能及其特点 掌握 rsync 语法及常用选项的功能 掌握 rsync 命令的三种基本使用方法 掌握如何筛选 rsync 的传输目标 掌握使用 rsync 进行镜像和增量备份 ...
- Working with PDF files in C# using PdfBox and IKVM
I have found two primary libraries for programmatically manipulating PDF files; PdfBox and iText. T ...
- python之pymongo
引入 在这里我们来看一下Python3下MongoDB的存储操作,在本节开始之前请确保你已经安装好了MongoDB并启动了其服务,另外安装好了Python的PyMongo库. MongoDB 数据库安 ...