专栏首页Web技术研发MySQL系列 | 悲观锁与乐观锁最佳实践

MySQL系列 | 悲观锁与乐观锁最佳实践

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,缩写“PCC”)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据。如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。

悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

一、理解悲观锁与乐观锁

获取锁的前提:结果集中的数据没有使用排他锁或共享锁时,才能获取锁,否则将会阻塞。

需要注意的是, FOR UPDATE 生效需要同时满足两个条件时才生效:

  • 数据库的引擎为 innoDB
  • 操作位于事务块中(BEGIN/COMMIT)

二、理解悲观锁与乐观锁

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

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

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

三、悲观锁的流程

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。

如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

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

案例说明

1 明确指定主键,并且查询的数据存在,行锁(row lock)

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

2 明确指定主键,并且查询的数据 不存在,无锁

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;

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

测试说明

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

1、不开启事务,明确指定主键,且该数据存在

可以看出如果不在事务中,则查询加锁是无效的。

2、开启事务,明确指定主键,且该数据存在。提交和不提交事务

  • 开启事务,不提交事务,按照主键查询该语句,则第二个查询相同主键的的语句会阻塞。
  • 开启事务,不提交事务,按照主键查询该语句,则第二个查询不相同主键的的语句不会阻塞。
  • 开启事务,锁如果是回滚或者提交事务,会自动释放掉锁的。按照主键查询该语句,则第二个查询相同主键的的语句会自动解除阻塞(锁已经被释放掉了),查询结果。
  • 说明:通过上面的演示,可以清楚的看到,锁的是同一个记录(id = 9),记录(id = 8)并没有受到上一条记录的影响。

3、开启事务,明确指定主键,但该数据不存在

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

4、开启事务,不指定主键,且该数据存在

  • 窗口1 开启了事务,查询订单号 :order_no = "S640641911161202555241",查询数据正常。
  • 窗口2 也开启了事务,查询订单号 :order_no = "S640641911161202555241",查询阻塞,说明 窗口1 把该记录给锁住了(其实这里表已经被锁定, 而不是该记录了)。
  • 窗口3 开启了事务,查询订单号 :order_no = "S1807171712053133",查询阻塞,说明 窗口1 把该表给锁住了,不是同一条记录都不给查啊,阻塞的不要不要的。
  • 只有 窗口1 的记录回滚或者提交了,窗口2 的查询阻塞立刻释放掉了,但是 窗口3 依然在阻塞中(由于 窗口2 开启了事务导致的)。同理,回滚或者提交 窗口2 的事务后,窗口3 的记录也可以正常查询了。

5、开启事务,主键不明确,则会表锁(table rock)

  • 窗口1 开启了事务,查询主键 :id > 511 的记录,查询数据正常(3条记录)。
  • 窗口2 也开启了事务,查询订单号 :id > 511 的记录,查询阻塞,说明 窗口1 把该记录给锁住了(其实这里表已经被锁定, 而不是该行被锁住了)。
  • 窗口3 开启了事务,查询订单号 :id > 512 的记录,查询阻塞,说明 窗口1 把该表给锁住了,不是同一条记录都不给查啊,阻塞的不要不要的。
  • 只有 窗口1 的记录提交事务了,窗口2 的查询阻塞还是没有立刻释放掉,窗口3 的记录记录则是自动释放掉了。只能说明窗口1提交事务只是释放了整个表的锁,而不是这个行的锁。
  • 只有 窗口3 的记录提交(回滚)事务了,窗口2 的查询阻塞则会释放掉。证明了主键不明确开启事务查询后果很严重。只要有一个不明确的事务查询存在,则这个表一直是被锁定的,太可怕了!!!

四、小结

当执行 select ... for update时,将会把数据锁住,因此,我们需要注意一下锁的级别。MySQL InnoDB 默认为行级锁。当查询语句指定了主键时,MySQL会执行「行级锁」,否则MySQL会执行「表锁」。

常见情况如下:

  • 若明确指明主键,且结果集有数据,行锁;
  • 若明确指明主键,结果集无数据,则无锁;
  • 若无主键,且非主键字段无索引,则表锁;
  • 若使用主键但主键不明确,则使用表锁;

innoDB的行锁是通过给索引上的索引项加锁实现的,因此,只有通过索引检索数据,才会采用行锁,否则使用的是表锁。

五、总结

悲观锁采用的是「先获取锁再访问」的策略,来保障数据的安全。但是加锁策略,依赖数据库实现,会增加数据库的负担,且会增加死锁的发生几率。

此外,对于不会发生变化的只读数据,加锁只会增加额外不必要的负担。在实际的实践中,对于并发很高的场景并不会使用悲观锁,因为当一个事务锁住了数据,那么其他事务都会发生阻塞,会导致大量的事务发生积压拖垮整个系统。

本文分享自微信公众号 - Tinywan的杂货摊(TinywanIOT),作者:Tinywan

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-01

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • MQ教程 | RabbitMQ work queues 工作队列 [Java版](三)

    前提:使用 simple 队列的时候 我们应用程序在是使用消息系统的时候,一般生产者 P 生产消息是毫不费力的(发送消息即可),而消费者接收完消息 后的需要处理...

    Tinywan
  • Redis系列 | Redis5.0重量级新特性

    Tinywan
  • TP5系列 | 数据库迁移工具migration

    migration用谷歌翻译是移民的意思,在PHP中我们将它理解为迁移,将Migration用在数据库上就理解为数据库迁移咯。在migration开发之...

    Tinywan
  • Linux-ps命令(7)

    名称:ps(process status) 使用权限:所有使用者 说明:显示瞬间进程 (process) 的动态 示例: 比如输入ps查看进程,如下图: ? 其...

    张诺谦
  • 编码和Python的bytearray

    GBK UTF-8  是 字符集  编码方法   ===》 Apache  nginx

    py3study
  • 性能之殇:从冯·诺依曼瓶颈谈起

    电子计算机与信息技术是最近几十年人类科技发展最快的领域,无可争议地改变了每个人的生活:从生活方式到战争方式,从烹饪方式到国家治理方式,都被计算机和信息技术彻底地...

    机器之心
  • 操作系统知识梳理共9次缺页

    第一章:概述 什么是操作系统? 是一段一直运行在计算机上的程序 是资源的分配者 向上管理软件向下管理硬件 为用户提供良好接口 中断的概念? 中断指当出现需要时,...

    张俊怡
  • 面试宝典_Python.运维开发.000

    1. 用PYTHON实现tail -f功能,默认显示最后15行,实时输出新增行?

    py3study
  • 深度学习的树木覆盖预测

    今天将尝试一个关于树覆盖预测的演示,其中展示了使用eo-learn进行机器学习/深度学习是多么容易。将训练U-net深度学习网络来预测树木覆盖。

    代码医生工作室
  • 玩转CVM之部署Caddy

    这是一个Web Server的时代,apache与nginx争奇斗艳,在追求极致性能的路上,你追我赶,没有最高,只有更高。但这又是一个追求个性化的时代,有些We...

    苏欣

扫码关注云+社区

领取腾讯云代金券