learn-tech/专栏/架构设计面试精讲/10如何回答MySQL的事务隔离级别和锁的机制?.md
2024-10-16 06:37:41 +08:00

157 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
10 如何回答 MySQL 的事务隔离级别和锁的机制?
上一讲,我讲了 MySQL 的索引原理与优化问题,今天我带你继续学习 MySQL 的事务隔离级别和锁的机制MySQL 的事务和锁是并发控制最基本的手段,在面试中,它们与 09 讲的索引一样,同样是 MySQL 重要的考察点。
案例背景
MySQL 的事务隔离级别Isolation Level是指当多个线程操作数据库时数据库要负责隔离操作来保证各个线程在获取数据时的准确性。它分为四个不同的层次按隔离水平高低排序读未提交 < 读已提交 < 可重复度 < 串行化
MySQL 隔离级别
读未提交Read uncommitted隔离级别最低隔离度最弱脏读不可重复读幻读三种现象都可能发生所以它基本是理论上的存在实际项目中没有人用但性能最高
读已提交Read committed它保证了事务不出现中间状态的数据所有数据都是已提交且更新的解决了脏读的问题但读已提交级别依旧很低它允许事务间可并发修改数据所以不保证再次读取时能得到同样的数据也就是还会存在不可重复读幻读的可能
可重复读Repeatable readsMySQL InnoDB 引擎的默认隔离级别保证同一个事务中多次读取数据的一致性解决脏读和不可重复读但仍然存在幻读的可能
可串行化Serializable选择可串行化意味着读取数据时需要获取共享读锁更新数据时需要获取排他写锁如果 SQL 使用 WHERE 语句还会获取区间锁换句话说事务 A 操作数据库时事务 B 只能排队等待因此性能也最低
至于数据库锁分为悲观锁和乐观锁悲观锁认为数据出现冲突的可能性很大乐观锁认为数据出现冲突的可能性不大那悲观锁和乐观锁在基于 MySQL 数据库的应用开发中是如何实现的呢
悲观锁一般利用 SELECT FOR UPDATE 类似的语句对数据加锁避免其他事务意外修改数据
乐观锁利用 CAS 机制并不会对数据加锁而是通过对比数据的时间戳或者版本号实现版本判断
案例分析
如果面试官想深挖候选人对数据库内部机制的掌握程度切入点一般是 MySQL 的事务和锁机制接下来我就从初中级研发工程师的角度出发从概念到实践带你掌握MySQL 事务和锁机制的高频考点
举例说明什么是脏读不可重复度和幻读三者虽然基础但很多同学容易弄混
MySQL 是怎么解决脏读不可重复读和幻读问题的
你怎么理解死锁
案例解答
怎么理解脏读不可重复读和幻读
脏读 读到了未提交事务的数据
事务并发时的脏读现象
假设有 A B 两个事务在并发情况下事务 A 先开始读取商品数据表中的数据然后再执行更新操作如果此时事务 A 还没有提交更新操作但恰好事务 B 开始然后也需要读取商品数据此时事务 B 查询得到的是刚才事务 A 更新后的数据
如果接下来事务 A 触发了回滚那么事务 B 刚才读到的数据就是过时的数据这种现象就是脏读
脏读面试关注点
脏读对应的隔离级别是读未提交只有该隔离级别才会出现脏读
脏读的解决办法是升级事务隔离级别比如读已提交
不可重复读 事务 A 先读取一条数据然后执行逻辑的过程中事务 B 更新了这条数据事务 A 再读取时发现数据不匹配这个现象就是不可重复读
事务并发时的不可重复读现象
不可重复读面试关注点
简单理解是两次读取的数据中间被修改对应的隔离级别是读未提交读已提交
不可重复读的解决办法就是升级事务隔离级别比如可重复度
幻读 在一个事务内同一条查询语句在不同时间段执行得到不同的结果集
事务并发时的幻读现象
事务 A 读了一次商品表得到最后的 ID 3事务 B 也同样读了一次得到最后 ID 也是 3接下来事务 A 先插入了一行然后读了一下最新的 ID 4刚好是前面 ID 3 加上 1然后事务 B 也插入了一行接着读了一下最新的 ID 发现是 5而不是 3 1
这时你发现在使用 ID 做判断或做关键数据时就会出现问题这种现象就像是让事务 B 产生了幻觉一样读取到了一个意想不到的数据所以叫幻读当然不仅仅是新增删除修改数据也会发生类似的情况
幻读面试关注点
要想解决幻读不能升级事务隔离级别到可串行化那样数据库也失去了并发处理能力
行锁解决不了幻读因为即使锁住所有记录还是阻止不了插入新数据
解决幻读的办法是锁住记录之间的间隙为此 MySQL InnoDB 引入了新的锁叫间隙锁Gap Lock所以在面试中你也要掌握间隙锁以及间隙锁与行锁结合的 next-key lock
怎么理解死锁
除了事务隔离级别很多同学在面试时经常会被面试官直奔主题地问谈谈你对死锁的理解要回答这样开放的问题你就要在脑海中梳理出系统化的回答思路死锁是如何产生的如何避免死锁
死锁一般发生在多线程两个或两个以上执行的过程中因为争夺资源造成线程之间相互等待这种情况就产生了死锁我在 06 讲也提到了死锁但是并没有讲它产生的原因以及怎么避免所以接下来我们就来了解这部分内容
线程死锁
比如你有资源 1 2以及线程 A B当线程 A 在已经获取到资源 1 的情况下期望获取线程 B 持有的资源 2与此同时线程 B 在已经获取到资源 2 的情况下期望获取现场 A 持有的资源 1
那么线程 A 和线程 B 就处理了相互等待的死锁状态在没有外力干预的情况下线程 A 和线程 B 就会一直处于相互等待的状态从而不能处理其他的请求
死锁产生的四个必要条件
互斥条件
互斥 多个线程不能同时使用一个资源比如线程 A 已经持有的资源不能再同时被线程 B 持有如果线程 B 请求获取线程 A 已经占有的资源那线程 B 只能等待这个资源被线程 A 释放
持有并等待
持有并等待 当线程 A 已经持有了资源 1又提出申请资源 2但是资源 2 已经被线程 C 占用所以线程 A 就会处于等待状态但它在等待资源 2 的同时并不会释放自己已经获取的资源 1
不可剥夺条件
不可剥夺 线程 A 获取到资源 1 之后在自己使用完之前不能被其他线程比如线程 B抢占使用如果线程 B 也想使用资源 1只能在线程 A 使用完后主动释放后再获取
循环等待
循环等待 发生死锁时必然会存在一个线程也就是资源的环形链比如线程 A 已经获取了资源 1但同时又请求获取资源 2线程 B 已经获取了资源 2但同时又请求获取资源 1这就会形成一个线程和资源请求等待的环形图
死锁只有同时满足互斥持有并等待不可剥夺循环等待时才会发生并发场景下一旦死锁一般没有特别好的方法很多时候只能重启应用因此最好是规避死锁那么具体怎么做呢答案是至少破坏其中一个条件互斥必须满足你可以从其他三个条件出发
持有并等待我们可以一次性申请所有的资源这样就不存在等待了
不可剥夺占用部分资源的线程进一步申请其他资源时如果申请不到可以主动释放它占有的资源这样不可剥夺这个条件就破坏掉了
循环等待可以靠按序申请资源来预防也就是所谓的资源有序分配原则让资源的申请和使用有线性顺序申请的时候可以先申请资源序号小的再申请资源序号大的这样的线性化操作就自然就不存在循环了
总结
我们花了两讲的时间 MySQL 数据库面试中的高频问题熟悉了一遍但是如果从数据库领域应用开发者角度出发至少还需要掌握以下几部分内容
数据库设计基础掌握数据库设计中的基本范式以及基础概念例如表视图索引外键序列号生成器等掌握数据库的数据类型的使用清楚业务实体关系与数据库结构的映射
数据库隔离级别掌握 MySQL 四种事务隔离级别的基础知识并进一步了解 MVCCLocking 等机制对于处理的进阶问题的解决还需要了解不同索引类型的使用甚至是底层数据结构和算法等
SQL 优化掌握基础的 SQL 调优技巧至少要了解基本思路是怎样的例如 SQL 怎样写才能更好利用索引知道如何分析 SQL 执行计划等
数据库架构设计掌握针对高并发等特定场景中的解决方案如读写分离分库分表等
当然在准备面试时我并不建议你找一堆书闷头苦读还是要从实际工作中从使用数据库出发并结合实践完善和深化自己的知识体系今天的内容就讲到这里我们下一讲见