| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 大数据 -> 15张图呈现数据库事务背后的并发原理 -> 正文阅读 |
|
[大数据]15张图呈现数据库事务背后的并发原理 |
本文分享自华为云社区《将数据库9种锁、3种读、4种隔离级别一次性串联起来,用15张图呈现背后数据库事务背后的并发原理》,作者: breakDawn。 前段时间开发时,正好遇到了2个进程同时更新一行记录时引发的bug,虽然问题最终解决了,但自己对背后的运行逻辑仍旧一头雾水。事后尝试简单翻了下各种博客资料,还有《高性能mysql》那本书时,发现大部分是将一堆八股文概念堆砌在一起,很少完整串联过这堆概念。 于是我重新完整学习了这些概念和底层原理, 通过一个转账问题的场景,将这些概念全部关联起来。 将下面这些数据库的概念单独拿出来时,相信很多人都有了解或者记忆过,但是将这些概念全部串联在一起时,可能就会很混乱。
首先,我们完全不考虑数据库引擎、隔离级别设置之类的,就当作你用一个超简陋的儿科级别数据库来存放和更新数据。 假设你的商城服务正好在同时执行如下的2种事情
李四在最开始余额只有0元钱。 可以看到李四明明没有钱,却扣费了,变成了很奇怪的-100元。 Q:那这个有问题的读过程叫什么? Q:在不修改数据库隔离级别的情况下, 我们可以如何用sql语句手动解决这个脏读? Q:刚才看到”锁住李四这一行“, 那么这个就叫行级锁。 另外表锁也可以单独用lock table的语法手动加锁 Q:如果一个事务A申请了行锁,锁住某一行, 另一个事务B申请了表锁,那B会被阻塞吗?
简单点说, 意向锁就是行锁操作用来阻塞表锁用的。 但行锁和行锁之间不会互相阻塞,除非行有冲突。 刚才看到的for update会限制其他并行事务的所有读写操作,而且是2个事务上都加了”for update“。 这里使用排他锁来解决脏读的原因是因为后面有查询余额+扣余额的代码,写这段代码的人必须做提前保护,以避免自己读到一个可能被修改的数据,导致判断和修改失误。 和排他锁对应的是“共享锁”,也就是熟知的读写锁。 Q:那么什么时候使用共享锁比排他锁要好呢? 可以看到没有查自身+更新自身的操作, 仅仅是查+更新其他表,表之间也互不关联,对余额的实时性也不是要求太高。
Q:那我加的共享锁(S锁)和排他锁(X)什么时候释放呢?是每次执行完update马上释放吗?
说人话, 就是在事务中需要加锁时再加锁, 直到commit完一次性解锁。 为什么要两阶段锁,看到的一句话是 Q:两阶段锁协议可以避免死锁吗? 重新回到张三李四转账+下单的场景上来。 这样就不需要特地加锁了,每次循环判断即可,前提是冲突发生概率比较低,阻塞时间比较短。 刚才一个小小的脏读,就已经解决了下面3个问题
继续回到脏读问题, 前面我们学习的所有概念,都是和数据库自身隔离级别无关,使用数据库的锁语法或者version版本号来避免。 但数据库发展这么强大,怎么可能需要我们频繁自己写这种复杂逻辑,于是数据库诞生了隔离级别设置。 前面会发生脏读的隔离级别, 叫做RU(read uncommited) Q:先来个小问题,RU级别没有任何锁,对吗? Q:当我们的数据库被设置成RC级别(Read commited)时, 可以解决脏读, 那么背后是怎么解决的呢?
LBCC其实就是类似前面手动用悲观锁的方式, 事务操作中查询时默认试图加锁,因此就可能被update的排他锁阻塞住,避免了脏读。 但代价就是效率很低。很多场景下,select的次数是远大于update的。 所以InnoDb 基于乐观锁的概念, 想了一个MVCC,自己在事务的背后实现了一套类似乐观锁的机制来处理这种情况。 确保了尽可能不在读操作上加锁, 排他锁只对更新操作生效。 Q:MVCC究竟是怎么做的呢? 简而言之
Q:MVCC机制下, 什么是快照读,什么是当前读?
Q:那么回到刚才的脏读问题, MVCC究竟是怎么在读不加锁的情况下, 解决脏读的? 总结这个图,就是
这样的话就保证了读的数据必须是已经完成提交的,是不是很简单? Q:如果事务B中不做余额判断,支持直接赊账+扣费, 那是不是会导致先扣费,然后回滚成0这样的情况? 换言之, update等操作, 还是会加锁,且用最新版本更新,避免了脏更新的问题,如下: Q:上面这个过程有什么隐患 因此RC隔离级别无法解决 “不可重复读的问题” Q:RR(可重复读,Repeat Read)的隔离级别又是怎么解决上面这个问题的? 因此RR机制改变了readView的生成方式, 每次读时只使用事务B最开始拿到的那个readView,这样永远就只取老的数据了。 Q:那读问题中的幻读又是什么? Q: RR隔离级别中的MVCC机制可以解决上面的问题吗? Q: 那如果像下面这样, 事务A连续做两次更新呢,单纯靠MVCC能避免更新操作的幻读么? Q: 那数据库怎么处理这种2次updete中间做insert的幻读情况呢? RR级别会启用一个叫”间隙锁“(Gap锁)的玩意,专门来防这样情况。 可以看到,订单D的插入过程被update过程的间隙锁拦住了,于是无法插入,置到事务结束才会释放。 Q: 那行锁、间隙锁、next-key锁是什么区别? Q: 如果name这个字段不是索引,而是普通字段,那间隙锁会怎么加? Q: 那是不是只要name是索引,就不会给整个表全加间隙锁了? Q: 刚才看到说RR可以解决2次select之间的幻读, 也能解决2次update之间的幻读, 那为什么很多资料里,仍然说RR不能解决幻读? 发现什么区别没, 事务B的insert操作,发生在了事务A的update之前。因此事务B的insert操作没有被间隙锁阻塞。 而update用的是当前读, 于是更新的数量和 最初select的数量匹配不上了。 Mysql官方给出的幻读解释是:只要在一个事务中,第二次select多出了row就算幻读,所以这个场景下,算出现幻读了。 这也就是下面这个图的来源: Q: 那串行化serializable隔离级别,为什么就能避免幻读了? 这就是我们文章最开头手动加锁的那个过程了。 |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 8:48:05- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |