前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL系列 | 悲观锁与乐观锁最佳实践

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

作者头像
Tinywan
发布2020-04-01 16:48:34
1.2K0
发布2020-04-01 16:48:34
举报
文章被收录于专栏:开源技术小栈

在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,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)

代码语言:javascript
复制
SELECT * FROM products WHERE id='3' FOR UPDATE;

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

代码语言:javascript
复制
SELECT * FROM products WHERE id='-1' FOR UPDATE;

3 无主键,表锁(table lock)

代码语言:javascript
复制
SELECT * FROM products WHERE name='Mouse' FOR UPDATE;

4 主键不明确,表锁(table lock)

代码语言:javascript
复制
SELECT * FROM products WHERE id<>'3' FOR UPDATE;

5 主键不明确,表锁(table lock)

代码语言:javascript
复制
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的行锁是通过给索引上的索引项加锁实现的,因此,只有通过索引检索数据,才会采用行锁,否则使用的是表锁。

五、总结

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Tinywan的杂货摊 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库管理
数据库管理(Database Management Center,DMC)是一个高效,安全,可靠的数据库一站式管理平台。DMC 提供可视化的库管理、实例会话管理、SQL 窗口、SQL 安全审计、SQL 变更审批、实时监控、操作审计等数据库管理能力,集成诊断优化和数据可视化分析能力,从而简化和规范数据库管理操作、降低数据库运维门槛、提升运维效率。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档