![](https://img.kancloud.cn/7b/c0/7bc03132c2e9c74ef5b5e90f7de920df_1000x390.png) ## 目录 [TOC] ## 什么是事务? **事务就是为了保证一组数据库操作,要么全部成功,要么全部失败。** 事务是在引擎层实现的,也就是说并不是所有引擎都可以使用事务,MyISAM 就不支持事务,这也是为什么会被 InnoDB 取代的原因。 ## 事务的四大特性? 原子性(**A**tomicity,或称不可分割性)、一致性(**C**onsistency)、隔离性(**I**solation,又称独立性)、持久性(**D**urability)。 * **原子性:**一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 * **一致性:**在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 * **隔离性:**数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。 * **持久性:**事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 ## 没有隔离级别的话,多事务并发进行会造成什么问题? * 脏读: A事务读取到了B事务未提交的内容,而B事务后面进行了回滚. * 不可重复读: 当设置A事务只能读取B事务已经提交的部分,会造成在A事务内的两次查询,结果竟然不一样,因为在此期间B事务进行了提交操作. * 幻读: A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据.造成"幻觉". ## 事务的隔离级别和各自解决的问题是什么? 隔离性可能会引入**脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)等问题**,为了解决这些问题就引入了“隔离级别”的概念。 **SQL 标准的事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable)**: * **读未提交**:一个事务还没提交时,它做的变更就能被别的事务看到。 * **读提交**:一个事务提交之后,它做的变更才会被其他事务看到。 * **可重复读:** 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 * **串行化:** 顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。 SQL标准中规定,针对不同的隔离级别,并发事务可以发生不同严重程度的问题,具体情况如下: | 隔离级别 | 脏读 | 不可重复读 | 幻读 | | --- | --- | --- | --- | | `读未提交` | 可能 | 可能 | 可能 | | `读提交` | 不可能 | 可能 | 可能 | | `可重复读` | 不可能 | 不可能 | 可能 | | `串行化` | 不可能 | 不可能 | 不可能 | ## InnoDB使用的是哪种隔离级别呢? InnoDB默认使用的是可重复读隔离级别. RR ## 数据库事务的使用的规范有哪些? 1. 控制事务大小,减少锁定的资源量和锁定时间长度。 2. 所有的数据检索都通过索引来完成,从而避免因为无法通过索引加锁而升级为表锁。 3. 减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。 4. 在业务条件允许下,尽量使用较低隔离级别的事务隔离。减少隔离级别带来的附加成本。 5. 合理使用索引,让innodb在索引上面加锁的时候更加准确。 6. 在应用中尽可能做到访问的顺序执行(串行)。 7. 如果容易死锁,就可以考虑使用表锁来减少死锁的概率。 ## InnoDB怎么实现的事务ACID特性? * redo log重做日志用来保证事务的持久性 * undo log回滚日志保证事务的原子性 * undo log+redo log保证事务的一致性 * 锁(共享、排他)用来保证事务的隔离性 undo log 实现如下两个功能:1.实现事务回滚 2.实现MVCC undo log和redo log记录物理日志不一样,它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。 推荐阅读,加深理解 : https://www.cnblogs.com/jianzh5/p/11643151.html ## InnoDB的事务为什么是原子性的? **InnoDB 引擎使用 undo log(归滚日志)来保证原子性操作**,你对数据库的每一条数据的改动(INSERT、DELETE、UPDATE)都会被记录到 undo log 中,比如以下这些操作: * 你插入一条记录时,至少要把这条记录的主键值记下来,之后回滚的时候只需要把这个主键值对应的记录删掉就好了。 * 你删除了一条记录,至少要把这条记录中的内容都记下来,这样之后回滚时再把由这些内容组成的记录插入到表中就好了。 * 你修改了一条记录,至少要把修改这条记录前的旧值都记录下来,这样之后回滚时再把这条记录更新为旧值就好了。 当事务执行失败或者调用了 rollback 方法时,就会触发回滚事件,利用 undo log 中记录将数据回滚到修改之前的样子。 ## InnoDB是如何保证隔离性的? **利用锁和 MVCC 机制**。这里简单的介绍一下 MVCC 机制,也叫**多版本并发控制**,在使用 READ COMMITTD、REPEATABLE READ 这两种隔离级别的事务下,每条记录在更新的时候都会同时记录一条回滚操作,就会形成一个版本链,在执行普通的 SELECT 操作时访问记录的版本链的过程,这样子可以使不同事务的读-写、写-读操作并发执行,从而提升系统性能。 ## 事务的持久性如何保证? 持久性的定义:**事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。** 要保证持久性很简单,就是每次事务提交的时候,都将数据刷磁盘上,这样一定保证了安全性,但是要知道如果每次事务提交都将数据写入到磁盘的话,频繁的 IO 操作,成本太高,数据库的性能极低,所以这种方式不可取。 InnoDB 引擎是怎么解决的?**InnoDB 引擎引入了一个中间层来解决这个持久性的问题,我们把这个叫做 redo log(归档日子)**。 为什么要引入 redo log?redo log 可以保证持久化又可以保证数据库的性能,相比于直接刷盘,redo log 有以下两个优势: * redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 * redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。 InnoDB 引擎是怎么做的?当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里面,并更新内存,这个时候更新就算完成了。当数据库宕机重启的时候,会将 redo log 中的内容恢复到数据库中,再根据 undo log和 binlog 内容决定回滚数据还是提交数据。 ## 事务的一致性,指的是什么? **一致性简单一点说就是数据执行前后都要处于一种合法的状态**,比如身份证号不能重复,性别只能是男或者女,数据库应该体现为现实世界的一个映射。 要保证数据库的数据一致性,要在以下两个方面做努力: * **利用数据库的一些特性来保证部分一致性需求**:比如声明某个列为`NOT NULL` 来拒绝`NULL`值得插入等。 * **绝大部分还是需要我们程序员在编写业务代码得时候来保证**。 ## MVCC是什么,如何实现? 多版本并发控制,MVCC是一种并发控制的方法,减少事务中需要锁定的行数。 ## InnoDB的MVCC实现原理是什么? >简答 在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号。数据会保存在某个时间点的快照。`RC`、`RR` 两种隔离级别的事务在执行普通的读操作时,通过访问版本链的方法,使得事务间的读写操作得以并发执行,从而提升系统性能 。 >加深理解 这两个列,一个保存了行的创建时间,一个保存了行的过期时间(删除时间)。当然存储的并不是实际时间,而是**系统版本号**(sytem version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。 **SELECT** InnoDB 会根据以下两个条件检查每行记录: 1. InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。 2. 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。 只有符合上述两个条件的记录,才能返回作为查询结果。 **INSERT** InnoDB为新插入的每一行保存当前系统版本号作为行版本号。 **DELETE** InnoDB为删除的每一行保存当前系统版本号作为行删除标识。 **UPDATE** InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。 保存这两个额外系统版本号,使大多数读操作都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。 > MVCC只在REPEATABLE READ和READ COMMITIED两个隔离级别下工作。其他两个隔离级别都和 MVCC不兼容 ,因为READ UNCOMMITIED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。 可参考: https://www.codercto.com/a/88775.html https://www.jianshu.com/p/8845ddca3b23 ## 可重复读(repeatable read)级别如何避免幻读? * 在快照读读情况下,mysql通过mvcc来避免幻读。 * 在当前读读情况下,mysql通过next-key来避免幻读。 >复习一下 **什么是幻读** 事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。 **什么是next-key锁** 可以简单的理解为X锁+GAP锁 **临键锁(Next-key Locks):**是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。 * * 临键锁主要是为了避免幻读。如果把事务的隔离级别降级为RC,临键锁则会失效。 **什么是快照读和当前读** * 快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析) * select \* from table where ?; * 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。 * select \* from table where ? lock in share mode; * select \* from table where ? for update; * insert into table values (…); * update table set ? where ?; * delete from table where ?; ## mysql的锁了解么,如何分类? 可以从四个维度给锁分类,分别如下: ![](https://img.kancloud.cn/65/55/65553d5c385b498b41982a4ba125e110_1440x710.png) ## 乐观锁和悲观锁是什么,如何实现? 1、乐观锁:先修改,保存时判断是够被更新过,应用级别,说白了就是自己写代码实现,是一种思想,可以基于版本号、更新时间戳可以实现, 2、悲观锁:先获取锁,再操作修改,数据库级别,比如sql后缀写 for update,是基于MySQL自带的功能。 ## 锁的粒度有哪几种? 表级锁:开销小,加锁快,粒度大,锁冲突概率大,并发度低,适用于读多写少的情况。 页级锁:BDB存储引擎 行级锁:Innodb存储引擎,默认选项 注意,innoDB中行级锁是加在索引上的,因此只有命中索引的情况才会是行级锁,不然是表级锁, ## 锁的兼容性对比 * S锁:也叫做读锁、共享锁,对应于 `select * from users where id =1 lock in share mode` * X锁:也叫做写锁、排它锁、独占锁、互斥锁,对应于 `select * from users where id =1 for update` 可参考: https://zhuanlan.zhihu.com/p/31875702 https://blog.csdn.net/qq_38238296/article/details/88362999