MySQL(一)--ACID四大特性以及隔离级别
MySQL(一)–ACID四大特性以及隔离级别
如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性:
⑴ 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚
,这和前面两篇博客介绍事务的功能是一样的概念,因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
⑵ 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态
。
拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
⑶ 隔离性(Isolation)隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离
。
即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
⑷ 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作
。
例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
以上介绍完事务的四大特性(简称ACID),现在重点来说明下事务的隔离性,当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性,在介绍数据库提供的各种隔离级别之前,我们先看看如果不考虑事务的隔离性,会发生的几种问题:
1,脏读:
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。例如:用户A向用户B转账100元,对应SQL命令如下
update account set money=money+100 where name=’B’; (此时A通知B)
update account set money=money - 100 where name=’A’;
当只执行第一条SQL时,A通知B查看账户,B发现确实钱已到账(此时即发生了脏读),而之后无论第二条SQL是否执行,只要该事务不提交,则所有操作都将回滚,那么当B以后再次查看账户时就会发现钱其实并没有转。
怎么解决脏读?
- ①用隔离级别(设置为repeatable read,可重复读的隔离级别)
- ②对数据增加版本号(乐观锁)==>读时version不一样,则放弃
2,不可重复读:
不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了
。
例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。
在某些情况下,不可重复读并不是问题,比如我们多次查询某个数据当然以最后查询得到的结果为主。但在另一些情况下就有可能发生问题,例如对于同一个数据A和B依次查询就可能不同,A和B就可能打起来了……
3,虚读(幻读):
幻读是事务非独立执行时发生的一种现象
。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
幻读和不可重复读: 都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。简答的理解就是:不可重复读是数据变了,幻读是数据的行数变了
幻读:
幻读是:一次事物里,多次查询后,结果集的个数不一致情况叫“幻读”。
mysql是怎样解决幻读的?
- ①多版本并发控制(mvcc)(快照读/一致性读)。一致性读:是通过mvcc为查询提供一个基于时间点的快照。这个查询只能看到自己之前提交的数据,而在查询开始之后,提交的数据是不可以看到的。
- ②next-key锁(当前读):next-key原理:当前数据行与上一条数据和下一条数据之间的间隙锁定,保证此范围内读取数据是一致的。next-key锁包括什么?包括记录锁(记录锁是:加在索引上的锁)和间隙锁(加在索引之间的锁)
现在来看看MySQL数据库为我们提供的四种隔离级别:
① Read uncommitted (读未提交):最低级别,任何情况都无法保证。(会造成脏读
)
② Read committed (读已提交或者叫不可重复读):=可避免脏读的发生(大多数数据库系统默认的是这个,但是MySQL不是,MySQL默认的是可重复读的级别
)
③ Repeatable read (可重复读):可避免脏读、不可重复读的发生。(但是会造成幻读的发生
)
④ Serializable (串行化):可避免脏读、不可重复读、幻读的发生。(最好的隔离级别
)
隔离性由低到高,并发性由高到底。
以上四种隔离级别最高的是Serializable级别
,最低的是Read uncommitted级别,当然级别越高,执行效率就越低
。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。在MySQL数据库中默认的隔离级别为Repeatable read (可重复读)。
在MySQL数据库中,支持上面四种隔离级别,默认的为Repeatable read (可重复读);而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。
如何保证事务ACID特性(也就是如何实现的):
原子性:
定义:
原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。
如果事务中一个 sql 语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。
实现原理:undo log
在说明原子性原理之前,首先介绍一下 MySQL 的事务日志。MySQL 的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等。
此外 InnoDB 存储引擎还提供了两种事务日志:
- redo log(重做日志)
- undo log(回滚日志)
其中 redo log 用于保证事务持久性;undo log 则是事务原子性和隔离性实现的基础。
下面说回 undo log。实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的 sql 语句
。
当事务对数据库进行修改时,InnoDB 会生成对应的 undo log。
如果事务执行失败或调用了 rollback,导致事务需要回滚,便可以利用 undo log 中的信息将数据回滚到修改之前的样子。
当发生回滚时,InnoDB 会根据 undo log 的内容做与之前相反的工作:
-
对于每个 insert,回滚时会执行 delete。
-
对于每个 delete,回滚时会执行 insert。
-
对于每个 update,回滚时会执行一个相反的 update,把数据改回去。
生成的 undo log 中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到 update 之前的状态。
持久性:
定义:
持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
实现原理:redo log
下面先聊一下 redo log 存在的背景:
InnoDB 作为 MySQL 的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘 IO,效率会很低
。
为此,InnoDB 提供了缓存(Buffer Pool),Buffer Pool 中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:
当从数据库读取数据时,会首先从 Buffer Pool 中读取,如果 Buffer Pool 中没有,则从磁盘读取后放入 Buffer Pool。
当向数据库写入数据时,会首先写入 Buffer Pool,Buffer Pool 中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)
。
Buffer Pool 的使用大大提高了读写数据的效率,但是也带来了新的问题:如果 MySQL 宕机,而此时 Buffer Pool 中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
于是,redo log 被引入来解决这个问题
:当数据修改时,除了修改 Buffer Pool 中的数据,还会在 redo log 记录这次操作;当事务提交时,会调用 fsync 接口对 redo log 进行刷盘
。
如果 MySQL 宕机,重启时可以读取 redo log 中的数据,对数据库进行恢复。
既然 redo log 也需要在事务提交时将日志写入磁盘,为什么它比直接将 Buffer Pool 中修改的数据写入磁盘(即刷脏)要快呢?
主要有以下两方面的原因:
-
刷脏是随机 IO,
因为每次修改的数据位置随机,但写 redo log 是追加操作,属于顺序 IO
。 -
刷脏是以数据页(Page)为单位的,MySQL 默认页大小是 16KB,一个 Page 上一个小修改都要整页写入;而 redo log中只包含真正需要写入的部分,无效 IO 大大减少
。
redo log 与 binlog:
我们知道,在 MySQL 中还存在 binlog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的。
1,作用不同:
- redo log 是用于 crash recovery 的,保证 MySQL 宕机也不会影响持久性;
- binlog 是用于 point-in-time recovery 的,保证服务器可以基于时间点恢复数据,此外 binlog还用于主从复制。
2,层次不同:
- redo log 是 InnoDB 存储引擎实现的,
- 而 binlog 是 MySQL 的服务器层(可以参考文章前面对 MySQL 逻辑架构的介绍)实现的,同时支持 InnoDB 和其他存储引擎。
3,内容不同:
- redo log 是物理日志,内容基于磁盘的 Page。
- binlog 是逻辑日志,内容是一条条 sql。
4,写入时机不同:
-
redo log 的写入时机相对多元。前面曾提到,当事务提交时会调用 fsync 对 redo log进行刷盘;这是默认情况下的策略,修改 innodb_flush_log_at_trx_commit参数可以改变该策略,但事务的持久性将无法保证。
除了事务提交时,还有其他刷盘时机:如 master thread 每秒刷盘一次 redo log 等,这样的好处是不一定要等到 commit 时刷盘,commit 速度大大加快。
-
binlog 在事务提交时写入。
隔离性:
定义:
与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。
隔离性是指事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
那么隔离性的探讨,主要可以分为两个方面:
-
(一个事务)写操作对(另一个事务)写操作的影响
:锁机制保证隔离性。 -
(一个事务)写操作对(另一个事务)读操作的影响
:MVCC 保证隔离性。
锁机制的基本原理可以概括为:
- 事务在修改数据之前,需要先获得相应的锁。
- 获得锁之后,事务便可以修改数据。
- 该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
还会涉及到行锁,表锁啥的…
MVCC: MVCC 全称 Multi-Version Concurrency Control,即多版本的并发控制协议。
下面的例子很好的体现了 MVCC 的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)——在 T5 时刻,事务 A 和事务 C 可以读取到不同版本的数据。
MVCC 最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB 实现 MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列(也可以称之为标记位)和 undo log
。
其中数据的隐藏列包括了该行数据的版本号、删除时间、指向 undo log 的指针等等。
MVCC的一些相应的解释,可以看看这个文章数据库MVCC 隔离级别
当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的 undo log,从而实现 MVCC;
小结:概括来说,InnoDB 实现的 RR,通过锁机制、数据的隐藏列、undo log 和类 next-key lock,实现了一定程度的隔离性,可以满足大多数场景的需要
。
一致性:
定义:
一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。
实现:
可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。
实现一致性的措施包括:
- 保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证。
- 数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等。
- 应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致。
总结:
下面总结一下 ACID 特性及其实现原理:
-
原子性:语句要么全执行,要么全不执行,是事务最核心的特性。事务本身就是以原子性来定义的;
实现主要基于 undo log。
-
持久性:保证事务提交后不会因为宕机等原因导致数据丢失;
实现主要基于 redo log
。 -
隔离性:保证事务执行尽可能不受其他事务影响;InnoDB 默认的隔离级别是 RR(REPEATABLE READ也就是可重复读),
RR 的实现主要基于锁机制、数据的隐藏列、undo log 和类 next-key lock 机制
。 - 一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障。
感谢并参考:
https://blog.csdn.net/qfc8930858/article/details/88530137
https://blog.csdn.net/enmotech/article/details/86710350
本文地址:https://blog.csdn.net/chen772209/article/details/107370164
上一篇: HCIE实验:BGP的双平面架构
下一篇: 项目开发中如何定义Redis 使用规范
推荐阅读
-
数据库事务的四大特性以及事务的隔离级别
-
数据库事务的四大特性以及隔离级别
-
关于MySQL数据库四大特性及数据库隔离级别知识详解
-
MySQL(一)--ACID四大特性以及隔离级别
-
事务的四大特性,以及隔离级别
-
数据库事务的四大特性以及事务的隔离级别
-
【面试高频!】数据库事务/事务的作用/数据库事务ACID属性 / 特性/事务的隔离性/数据库事务隔离级别/ 数据库的隔离级别导致问题/数据库事务的使用/mysql的事务死锁/ 快速解决mysql死锁问
-
MySQL事务(transaction)的四大特性ACID、隔离性问题及隔离级别
-
MySQL事务,事务的四大特性以及四种隔离级别
-
MySql的四大特性以及隔离级别