为了保证一致性
1.ACID
事务具有四个基本特性,也就是通常所说的 ACID 特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。
A原子性:意味着事务中的所有操作要么全部完成,要么全部不完成,它是不可分割的单位。如果事务中的任何一个操作失败了,整个事务都会回滚到事务开始之前的状态,如同这些操作从未被执行过一样。
B一致性:确保事务从一个一致的状态转换到另一个一致的状态。比如在银行转账事务中,无论发生什么,转账前后两个账户的总金额应保持不变。假如 A 账户(100 块)给 B 账户(10 块)转了 10 块钱,不管成功与否,A 和 B 的总金额都是 110 块。
C隔离性:意味着并发执行的事务是彼此隔离的,一个事务的执行不会被其他事务干扰。
隔离性主要是为了解决事务并发执行时可能出现的问题,如脏读、不可重复读、幻读等。通过事务隔离级别(如读未提交、读已提交、可重复读、串行化)来实现事务的隔离性。
D持久性:确保事务一旦提交,它对数据库所做的更改就是永久性的,即使发生系统崩溃,数据库也能恢复到最近一次提交的状态。通常,持久性是通过数据库的恢复和日志机制来实现的,确保提交的事务更改不会丢失。
2.那 ACID 靠什么保证的呢?
A:通过 undo log 来确保原子性。如果事务执行失败,MySQL 会使用undo log
中的旧值来回滚事务开始前的状态
C:其他三个特性都得到了保证,那么一致性就自然而然得到保证了。
I:MySQL 定义了多种隔离级别,通过 MVCC 来确保每个事务都有专属自己的数据版本,从而实现隔离性。
D:MySQL 会先将更改记录到 redo log 中。当 redo log 填满时,MySQL 再将这些更改写入数据文件中。如果 MySQL 在写入数据文件时发生崩溃,可以通过 redo log 来恢复数据文件,从而确保持久性
3.并发事务带来了哪些问题?
脏读:当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。读到了别人事务中没提交的数据 脏数据 ,本次读取 叫 脏读。(MVCC解决)
不可重复读:指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。(MVCC解决)
幻读:幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。(临键锁来解决)
不可重复度和幻读区别:
不可重复读的重点是修改(MVCC解决),幻读的重点在于新增或者删除(临键锁来解决)。
4.事务的隔离级别有哪些?MySQL 的默认隔离级别是什么?
读未提交(Read Uncommitted):当前事务可以读取未被其他事务提交的数据
读已提交(Read Committed):只能读取已经被其他事务提交的数据,可以避免“脏读”现象。但不可重复读和幻读问题仍然存在。
可重复读(Repeatable Read):确保在同一事务中多次读取相同记录的结果是一致的,即使其他事务对这条记录进行了修改,也不会影响到当前事务。可重复读是 MySQL 默认的隔离级别,避免了“脏读”和“不可重复读”,但可能会出现幻读。
串行化(Serializable):最高的隔离级别,通过强制事务串行执行来避免并发问题,可以解决“脏读”、“不可重复读”和“幻读”问题。但会导致大量的超时和锁竞争问题。
5.事务的各个隔离级别都是如何实现的?
读未提交是如何实现的?
不提供任何锁机制来保护读取的数据
读已提交&可重复读是如何实现的?
读已提交和可重复读通过 MVCC 机制中的 ReadView 来实现。
- READ COMMITTED:每次读取数据前都生成一个 ReadView,保证每次读操作都是最新的数据。
- REPEATABLE READ:只在第一次读操作时生成一个 ReadView,后续读操作都使用这个 ReadView,保证事务内读取的数据是一致的。
串行化是如何实现的?
事务在读操作时,必须先加表级共享锁,直到事务结束才释放;事务在写操作时,必须先加表级排他锁,直到事务结束才释放。
6.并发事务的控制方式有哪些?
锁
MySQL 中主要是通过 读写锁 来实现并发控制。
- 共享锁(S 锁):又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)。
- 排他锁(X 锁):又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条记录加任何类型的锁(锁不兼容)。
MVCC
7.锁
MyISAM 仅仅支持表级锁,一锁就锁整张表,这在并发写的情况下性非常差。InnoDB 不光支持表级锁,还支持行级锁,默认为行级锁。
表级锁和行级锁对比:
- 表级锁: MySQL 中锁定粒度最大的一种锁(全局锁除外),是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。不过,触发锁冲突的概率最高,高并发下效率极低。表级锁和存储引擎无关,MyISAM 和 InnoDB 引擎都支持表级锁。
- 行级锁: MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁 ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存储引擎有关,是在存储引擎层面实现的。
8.行级锁的使用有什么注意事项?
当我们执行 UPDATE
、DELETE
语句时,如果 WHERE
条件中字段没有命中唯一索引或者索引失效的话,就会导致扫描全表对表中的所有行记录进行加锁。
9.InnoDB 行锁分类
InnoDB 行锁是通过对索引数据页上的记录加锁实现的。默认是临键锁。
- 记录锁(Record Lock):也被称为记录锁,属于单个行记录上的锁。
- 间隙锁(Gap Lock):锁定一个范围,不包括记录本身。
- 临键锁(Next-Key Lock):Record Lock+Gap Lock,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题(MySQL 事务部分提到过)。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。
10.MVCC
多版本并发控制方法,即对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。保证了事务的隔离性
MVCC 允许读操作访问数据的一个旧版本快照,同时写操作创建一个新的版本,这样读写操作就可以并行进行,不必等待对方完成。
11.MVCC怎么实现的
InnoDB 存储引擎,MVCC 是通过版本链(隐藏字段)和 ReadView 机制来实现的。
隐藏字段:
在 InnoDB 中,每一行数据都有两个隐藏的列:一个是 DB_TRX_ID,另一个是 DB_ROLL_PTR。
DB_TRX_ID
,表示最后一次插入或更新该行的事务 id。DB_ROLL_PTR
,指向 undo 日志记录的指针,这个记录包含了该行的前一个版本的信息。通过这个指针,可以访问到该行数据的历史版本。
ReadView(读视图):
ReadView 它用于确定在特定事务中哪些版本的行记录是可见的。主要用来处理隔离级别为"可重复读"和"读已提交"的情况。因为在这两个隔离级别下,事务在读取数据时,需要保证读取到的数据是一致的,即读取到的数据是在事务开始时的一个快照。