当今是分布式架构的天下,在这种架构当中存在着各式各样的锁:大到分布式锁,小到代码的锁,还有数据库的锁。尽管这些各不相同的锁令人头疼,我们对锁的语义却都是相同的:

同一时刻只有获取锁的线程可以运行,其他线程必须等到锁释放才有可能执行。

从上面的定义我们可以看出锁是作用于线程的,并且是排他的。此外要注意以上定义是针对写入操作的,对于读取操作并不适合,因为读取的情况往往不需要锁。

今天我们的主题是 MySQL 的锁,首先我们需要知道 MySQL 的锁在这么多“锁”之中处于什么样的地位。只有认清楚 MySQL 锁的位置,我们才能更好地使用它。我们从大家相对熟悉的应用的锁说起。

应用的锁和数据库的锁

无论你使用什么编程语言,这门语言都会有锁的语法,比如 Java 中的 Synchronized、Go 中的 Mutex 等等。确实,对业务场景来说,即便数据库不支持锁机制,也可以通过代码来实现“同一时刻只有一个线程可以访问”的效果。更多情况下我们会选择在应用中使用锁,因为开发者更熟悉自己所掌握的代码,数据库对于他们来说只是外部存储介质,能实现增删改查就行了。

举个实际的例子,我们经常要做的唯一性校验,它既可以放在应用中实现,也可以利用数据库的唯一性约束,二者都可以很好地实现。

而在更复杂的场景中,比如分布式环境,通常就要引入分布式锁了,因为我们要找到一个可以集中管控“锁”的中枢,这个中枢通常是 Redis。你还能找到别的中枢吗?对,另一个中枢就是 MySQL 本身。是不是有种骑马找马的感觉?如果 MySQL 本身就可以充当锁,我们又何必苦苦寻找另一个中枢呢?显然,利用 MySQL 的锁大大降低了系统复杂度,它把一个复杂分布式架构简化成为“传统的应用 + 数据库”模型。

悲观锁

下面我们先来聊聊 MySQL 的悲观锁,悲观锁是显性的锁,你可以清楚地看到锁相关的语法。并且锁需要 MySQL 的 InnoDB 引擎和事务才可以生效,这是前提。

悲观锁的语法是什么呢?很多同学会想到 select for update 语句,但这么想并不准确。实际上 MySQL 悲观锁的语法关键字就是 update,在同一时间执行同一行操作的两条 update 语句之间只有一个会执行,另一个只能等待。

后来人们发现除了 update 需要锁,很多情况下 select 也需要锁,从 select 获取锁开始到事务提交或者回滚,整个过程都具有排他性。因此人们需要为 select 提供类似 update 悲观锁的能力,于是就有了 select for update 语句,它代表让 select 语句像 update 一样可以锁定行,直到事务执行完才能释放,我们来看下具体例子:

begin;
--A 操作
select name from user where id=1 for update;
--B 操作
commit;

这段事务中 select for update 夹在 A 操作和 B 操作之间,当执行到 select for update 时会对 ID 为 1 的行加锁,B 操作执行时其他线程不能操作当前的行直到 commit 执行才能释放锁。我们再来看一段代码:

begin;
--A 操作
update user set name='lg' where id=1;
--B 操作
commit;

这次我们把 select for update 语句换成 update 语句,当执行到 update 时会对 ID 为 1 的行加锁,B 操作执行时其他线程不能操作当前的行,直到 commit 执行才能释放锁。

可见,上面两段代码在锁的效力上完全一样。

那么如果想观察“锁”的过程该怎么办呢?其实只要让 B 操作执行的久一些就可以了,这时可以使用 MySQL 的 sleep 函数。

begin;
--A 操作
update user set name='lg' where id=1;
select sleep(20);
commit;

上面执行完 update 语句以后将 sleep 20 秒再 commit,这时如果你打开另一个窗口执行其他 update 操作,那么你的行为将被阻塞。

默认情况下,MySQL 只会锁定唯一索引行,如果没有唯一索引,将会锁表。

SELECT * FROM user WHERE id=1 FOR UPDATE;

比如上面,ID 为 1 的行确实存在,并且 ID 是唯一索引,因此会锁定这一行,这就是我们常说的行级锁。如果 ID 对应的行不存在,则不会产生任何锁。

SELECT * FROM user WHERE id>1 FOR UPDATE;

我们稍微改一下这个 SQL 条件,此时 ID 指向一个不明确的,或者是无限的范围,MySQL 找不到具体的行,就会进行表锁

SELECT * FROM user WHERE name='lg' FOR UPDATE;

之后id为2的name变成1234567了。

我们再改一下这个 SQL,name 列没有唯一索引,MySQL 依然会进行表锁

总而言之,MySQL 行级锁锁的是有限的唯一索引,找不到有限的唯一索引,就会锁表。

乐观锁

MySQL 乐观锁是基于悲观锁实现的,从这个角度来看也可以认为 MySQL 本身并没有乐观锁,但是可以通过巧妙的方法来实现。

乐观锁是悲观锁相对的存在,它假设数据不会发生冲突,因此在数据进行 commit 的时候,才会正式对数据的冲突情况进行检测,如果发现冲突了,则直接返回用户错误信息,让用户决定下一步如何做。乐观锁实现一般都是增加一列版本号 version,当更新数据时对版本号进行更新。

使用版本号时,可以在数据初始化时指定一个版本号,在每次数据更新时都对版本号进行 +1 操作。

select * from user where id=1
update user set name=#{name},version=version+1 where id=1 and version=0;

假设 2 个线程分别执行上面的语句,线程 A 和线程 B 为 name 传入不同的值,其中 select 语句是可以并行执行的,假设 select 语句执行以后返回的 version 字段值为 0,那么下面我们就该执行 update 语句。而因为 MySQL 悲观锁的特性,两个线程不可能同时 update 一条数据,所以在 update 同一条数据的时候,是有先后顺序的,只有在第一个线程执行完 update,才能释放行锁,让第二个线程继续进行 update。

第一个线程执行完成后,version 字段值将变成 1,所以第二个线程修改失败,实现了乐观锁控制。

今天我们聊了 MySQL 的悲观锁和乐观锁,其实 MySQL 只有悲观锁,乐观锁是悲观锁的一种巧妙用法。使用 MySQL 的锁可以简化我们的架构,尤其是分布式环境下,不需要再引入其他的锁。

当然并不是所有场景都适合使用 MySQL 的锁。虽然现在 MySQL 分表的场景也很多,但是遇到对性能特别敏感的场景比如秒杀,多数情况还是使用 Redis 之类的 NoSQL 来实现,这种情况下分布式锁就有用武之地了。在流量不是特别极端的情况下,MySQL 可以很好地支持业务,使用 MySQL 自带的锁也是完全足够的。

补充一句,一旦你决定了要用 MySQL 的锁,一定要评估好流量,做好压测工作,整体来说 MySQL 的性能还是很强劲的。

最后希望我的分享可以帮到你,欢迎你在评论区给我留言,也欢迎把这篇文章分享给你的朋友!

MySQL 的悲观锁和乐观锁如何使用?相关推荐

  1. Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁) |--悲观锁( ...

  2. mysql隔离级别与悲观锁、乐观锁

    2019独角兽企业重金招聘Python工程师标准>>> 1.什么是悲观锁,乐观锁 悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性.但随之而来的就是数据库性能的大 ...

  3. MySQL:行锁、表锁、乐观锁、悲观锁、读锁、写锁

    1.锁的分类 1.1从对数据操作的类型来分 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响. 结论1: --如果某一个会话 对A表加了read锁,则 该会话 可以对A表进行读操作 ...

  4. 阿里P8架构师谈:MySQL行锁、表锁、悲观锁、乐观锁的特点与应用

    我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突).如何保证数据并发访问的一致性.有效性,是所有数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素,从 ...

  5. 悲观锁和乐观锁_带你了解MySQL中的乐观锁与悲观锁

    在并发控制编程中锁是一个非常重要的概念,锁对于数据和业务一致性的保证起到关键作用,锁可以是程序层面的,也可以是数据库层面的,今天本文就通过MySQL来说明悲观锁与乐观锁两种常见的锁机制. 悲观锁 悲观 ...

  6. mysql共享锁使用方法_浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景...

    Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |-- ...

  7. mysql原子性和乐观锁_乐观锁 VS 悲观锁

    1.乐观锁 VS 悲观锁 乐观锁与悲观锁是一种广义上的概念,体现了看待线程同步的不同角度.在Java和数据库中都有此概念对应的实际应用. 1.1 概念悲观锁:对于同一个数据的并发操作,悲观锁认为自己在 ...

  8. mysql 乐观锁和悲观锁,MySQL中的悲观锁与乐观锁

    悲观锁与乐观锁是解决资源并发场景的解决方案 CREATE TABLE `order_stock` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID' ...

  9. mysql锁的应用场景_浅谈Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景

    Mysql共享锁.排他锁.悲观锁.乐观锁及其使用场景 一.相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |-- ...

  10. mysql锁机制——乐观锁、悲观锁;共享锁、排他锁、行表锁、间隔后码锁、MVCC 与 thinkphp的lock解析

    锁的引入 如果A有100元,同时对B.C转账,若处理是同时的,则此时同时读取A的余额为100元,在对两人转账后写回,A的余额不是0元而是50元.因此,为了防止这种现象的出现,要引入锁的概念,如只有在A ...

最新文章

  1. 剑指offer_第4题_重建二叉树
  2. 2019Java常见面试下
  3. python性能分析
  4. 设计模式(一)工厂模式Factory(创建型)
  5. 第六周 Word目录和索引
  6. 【蓝桥杯】基础练习 十进制转十六进制
  7. SAP CRM one order PERFORM read_header_ext的switch case分支
  8. 【Clickhouse】Clickhouse 运算符 操作符 算术,比较,取整,逻辑,哈希,条件 字符串函数
  9. c语言实现扫雷小游戏和扫雷源代码
  10. linux 格式化磁盘失败,linux格式化磁盘出错
  11. 2022 最新 IntelliJ IDEA 2022 详细配置步骤演示(图文版)
  12. 绝地求生大逃杀地图分析
  13. [翻译]在Windows版或MacOS版的Microsoft Edge上安装一个谷歌浏览器拓展
  14. tai game java hay,GGame - Kho tai Game, tin Game
  15. Maven 的 Maven Compiler Plugin 插件
  16. 射频卡机井灌溉控制器特点
  17. GreenPlum角色与权限控制
  18. 在使用反向代理访问的服务器上配置远程jupyterNotebook
  19. Codeforces与洛谷题目之间跳转油猴插件
  20. 营销策划 —— 论 营销策划书

热门文章

  1. Folx Pro for mac 5.19 Mac平台最好的下载神器中文版
  2. PLC与变频器屏蔽线连接的方法
  3. 机房环境维护保养的重要性
  4. C++,继承、虚函数解惑!
  5. 蓝桥杯每日一练:等差序列求和
  6. 如何选择条码标签设计打印软件?
  7. 施耐德PLC UNITY PRO XL 通过NOC模块与第三方硬件通讯
  8. 【Linux】使用U盘自动化安装Linux(VMware虚拟机)
  9. 苹果运行内存比较_iPhone手机的内存,为什么这几年一直没有像安卓这样升级
  10. AMD处理器,NVIDIA显卡下载pytorch问题