本文为【Mysql事务八股文合集】初版,后续还会进行优化更新,欢迎大家关注交流~
大家第一眼看到这个标题,不知道心中是否有答案了?在面试当中,面试官经常对项目亮点进行深挖,来考察你对这个项目亮点的理解以及思考!这个时候,你如果可以回答出面试官的问题,甚至是主动说出自己的思考,那在面试中是大大加分的~
什么是事务?
事务是逻辑的一组操作,要么都执行,要么都不执行
# 开启一个事务
START TRANSACTION;
# 多条 SQL 语句
SQL1,SQL2...
## 提交事务
COMMIT;
请你描述下事务的特性?
(ACID)
(一原吃哥)
原子性A 最小的执行单位,不允许分割 要么全部完成,要么完全不起作用
一致性C 前后,数据库从一个一致性状态转换到另一个一致性
隔离性I 并发 不被其他事务所干扰 事务独立
持久性D 提交后 改变是持久
怎么实现
原子性(redo 和 undo 日志)
在执行事务期间,将修改的数据写入 redo 日志,并在事务提交前将其持久化到磁盘,确保事务的持久性。如果事务发生故障或回滚操作,可以使用 undo 日志来撤销事务的修改,使数据回滚到事务开始之前的状态。
隔离性(锁机制和多版本并发控制)
持久性(redo 日志)
innodb的默认隔离级别以及使用的技术
默认隔离级别是可重复读RR,但是用到了MVCC的快照读和当前读技术以及next-key锁,所以可以解决幻读问题。对标SQL标准的可串行化。
对事务隔离级别的理解
(未提交读,提交读,可重复读,串行化)
READ_UNCOMMITTED(未提交读)
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可以重复读
解决了更新丢失,但还是可能会出现脏读
READ_COMMITTED(提交读)
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
(Oracle的隔离级别)解决了更新丢失和脏读问题
REPEATABLE_READ(可重复读)
对在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的
通常针对数据更新(UPDATE)操作。解决了更新丢失、脏读、不可重复读、但是还会出现幻读
SERIALIZABLE(串行化):
最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
但是这将严重影响程序的性能。通常情况下也不会用到该级别。解决了更新丢失、脏读、不可重复读、幻读(虚读)
只有串行化的隔离级别解决了全部这 3 个问题,其他的 3 个隔离级别都有缺陷。
【扩展:脏读、幻读或不可重复读】
【扩展:每种隔离级别分别解决了什么问题】
【扩展:实现原理】
实现原理
读未提交,采取的是"不加锁读"原理。
- 事务读不加锁,不阻塞其他事务的读和写
- 事务写阻塞其他事务写,但不阻塞其他事务读;
读已提交
“锁定读”
事务在读取数据时会对数据进行短暂的排他锁定,确保其他事务不能修改数据。
实现可重复读,MySQL 采用了 MVVC (多版本并发控制) 的方式。
每个事务在读取数据时会创建一个数据快照,并使用保存的快照来获取一致的结果。
串行化
- 所有SELECT语句会隐式转化为SELECT … FOR SHARE,即加共享锁。
- 读加共享锁,写加排他锁,读写互斥。如果有未提交的事务正在修改某些行,所有select这些行的语句都会阻塞。
可重复读怎么实现的
MVCC(多版本并发控制): MySQL使用MVCC来实现可重复读。在MVCC中,每行数据都会有一个创建版本号(也称为事务ID或时间戳)和一个过期版本号。当一个事务开始时,它会获得一个唯一的事务ID。在事务执行期间,它只能看到创建版本号小于或等于自己事务ID的数据。这确保了事务只会读取到它开始之前已经提交的数据,而不会读取到正在进行中的其他事务的数据。
读已提交和可重复读中 readView 有什么区别
在读已提交中,每个查询都会创建一个新的readView,读取最新的已提交事务的数据。在可重复读中,一个事务的整个生命周期内只创建一个readView,始终读取相同的版本,以确保事务的一致性视图。
如何解决脏读
通过排他锁和共享锁完成。事务A加排他锁进行修改,事务结束前不会释放锁。事务B想要读取,要共享锁,但是由于有了排他锁,因此共享锁阻塞。等事务A结束,才能读取得到。
既然innodb的可重复读已经解决了幻读问题,那么和串行化的实现区别是什么
可重复读通过next-key锁解决了当前读下的幻读问题。而串行化是通过加表锁来解决的。
解释下什么叫脏读、不可重复读和幻读?
(并发事务带来的)
脏读
(读取未提交数据)
(一个事务正在访问数据并进行了修改,而这种修改还未提交,另外一个事务也访问到了这个数据,使用了这个还未修改的数据)
(读未提交的数据)
不可重复读
(前后多次读取,数据内容不一致)
在一个事务内,多次读同一数据,另一个事务访问修改,导致数据读取结果不一样
(多次结果不同,查询期间另一个事务修改数据)
幻读
(前后多次读取,数据总量不一致)
(类似不可重复读,在发生一个事务1读取几行数据,接着另一个事务插入了一些数据,导致第一个事务1多一些原本不存在的,像是幻觉,所以叫幻读)
丢失修改
一个事务读取数据,另一个事务也访问,都修改了数据,但丢失了事务1修改的数据
(两个同时修改)
不可重复读和脏读的区别
脏读是读取另一个事务未提交的脏数据
不可重复读是读取了前一事务提交的数据
可重复读(RR)怎么解决幻读
在可重复读的情况下,MySQL引入MVCC,但MVCC并没有真正解决了幻读。
可重复读开启了间隙锁,而间隙锁解决了幻读。
所以在可重复读下用间隙锁或next key锁才可以防止幻读。不使用间隙锁是无法解决幻读的。其实在读提交下检测唯一索引的唯一性也会开启间隙锁。
next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)
解决方案
不可重复读是读取了其他事务更改的数据,针对update操作
解决:
使用行级锁,锁定该行
幻读是读取了其他事务新增的数据,针对insert和delete操作
解决:
(1)使用表级锁,锁定整张表
(2)间隙锁是innodb中行锁的一种, 但是这种锁锁住的却不止一行数据,他锁住的是多行,是一个数据范围。间隙锁的主要作用是为了防止出现幻读,但是它会把锁定范围扩大,
(3)MVCC也可以
Mysql默认的隔离级别
MySQL默认采用的 REPEATABLE_READ隔离级别。(可重复读)
Oracle 默认采用的 READ_COMMITTED 隔离级别。(提交读)
谈谈对MVCC的了解
(相当于给我们的MySQL数据库拍个“快照”)
(版本号,读只读事务前的快照,并发读写性能高,解决脏读、幻读、不可重复读,但不能解决更新丢失问题)
用于实现读已提交和可重复读取隔离级别。
在数据库中维护多个版本的数据来支持并发事务的同时读取和写入操作
MVCC的核心思想是每个事务在读取数据时看到的是一致性的快照,而不会受到其他并发事务的修改的影响。它基于以下两个基本概念:
- 版本号:每个数据项都有一个与之关联的版本号,用于标识数据的版本。版本号可以是递增的序列号、时间戳或其他唯一标识。
- 快照读取:事务在读取数据时,会根据自身的事务时间戳(或其他标识)选择合适的版本进行读取。只有那些在事务开始之前创建的、版本号小于等于事务时间戳的数据才能被读取,而在事务期间创建或修改的数据对该事务不可见。
多版本并发控制(MVCC)
一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,
读操作只读该事务开始前的数据库的快照。
解决以下问题:
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能;
- 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
MVCC的优势在于读操作不会阻塞写操作,也不会读取到脏数据。每个事务读取的是一致性的数据快照,因此可以实现高并发性和较好的隔离性。
实现
版本号
它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的
当前读和快照读
select 快照读
快照读是读取事务开始前的数据样子,不受其他事务修改的影响,但可能遇到幻读问题。
(像不加锁的select操作就是快照读,即不加锁的非阻塞读)
会对数据修改的操作(update,insert,delete)都是采用当前读的模式
当前读是读取最新的数据,会反映其他事务的修改,确保数据一致性,但可能会导致锁竞争和性能开销。
(select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读)
说白了MVCC就是为了实现读-写冲突不加锁,而这个读指的就是快照读, 而非当前读,当前读实际上是一种加锁的操作,是悲观锁的实现
RR和RC下MVCC有什么区别
"RR" 代表 "Repeatable Read"(可重复读)
"RC" 代表 "Read Committed"(已提交读)
- 在RR中,只要事务开始之后读了一次数据,那么就会生产快照,只会读都会直接读这个快照,因此不论别的事务怎么改动,都是可重复读的。
- 而RC中,每次读都会生成一次快照,所以不可重复读。
什么情况下mysql事务会失效?
- DDL语句(CREATE TABLE, ALTER TABLE等)会隐式提交事务
- LOCK TABLE语句会隐式提交事务
- 用户执行COMMIT或ROLLBACK时
- 发生致命错误(如服务器崩溃)时
- 超时(事务执行时间太长)时
能否用mysql实现分布式锁
可以。MySQL提供了基于表的分布式锁实现方式,即使用一个专门的表来实现分布式锁。具体做法是:
- 创建一个专门的表用于锁定
- 事务开始时尝试获取锁,获取成功则执行业务逻辑,最后释放锁
- 获取锁失败则重试或放弃
查询语句会开启事务吗,update语句会吗,为什么
- 查询语句(SELECT)一般不会开启事务,除非显式地开启事务
- Update语句会隐式地开启事务,因为它会修改数据,需要遵循ACID原则
- 事务的开启和提交/回滚与DML语句(INSERT/UPDATE/DELETE)有关,而非SELECT
mysql 数据库事务回滚如何实现?
MySQL使用UNDO日志来实现事务回滚。UNDO日志记录了事务执行过程中的所有修改操作,当需要回滚时,MySQL会按照UNDO日志的反向操作来撤销之前的修改。
MySQL宕机重启了,怎么知道哪些事务是需要回滚的哪些是需要提交的?
MySQL宕机重启后如何知道哪些事务需要回滚:
MySQL会依赖于REDO日志和undo日志来进行恢复。
-
- REDO日志记录了已提交事务的修改,用于恢复已提交的数据
- UNDO日志记录了未提交事务的修改,用于回滚未提交的事务
MySQL在重启时会扫描这两个日志,根据日志内容来判断哪些事务需要提交哪些需要回滚。
mysql事务实现的原理
(从redo log、bin log来思考)
- REDO log用于记录数据页的修改,确保数据库在crash后可以恢复到最后一次提交的状态
- BIN log用于记录所有数据库的变更操作,用于主从复制和备份恢复
mysql事务的死锁
当两个或多个事务在同时访问相同的数据资源且互相等待对方释放资源时,就会产生死锁。MySQL会检测到死锁的发生,并选择牺牲其中一个事务,让其他事务可以顺利完成。被牺牲的事务会收到"Deadlock found when trying to get lock; try restarting transaction"的错误。
后期新的八股文合集文章会继续分享,感兴趣的小伙伴可以点个关注~
更多精彩内容以及免费资料请关注公众号:绝命Coding