----以MySQL的InnoDB介绍
事务其实就是以一组访问或者更新数据库的各种数据项的一个执行单元,可以是一条也可以是多条执行操作。
理论上说,事务有着极其严格的定义,它必须同时满足四个特性,即通常所说的事务的ACID特性。值得注意的是,虽然理论上定义了严格的事务要求,但是数据库厂商出于各种目的,并没有严格去满足事务的ACID标准。例如,对于MySQL的
NDB Cluster
引擎来说,虽然其支持事务,但是不满足D的要求,即持久性的要求。对于Oracle数据库来说,其默认的事务隔离级别为READ COMMITTED
,不满足I的要求,即隔离性的要求。虽然在大多数的情况下,这并不会导致严重的结果,甚至可能还会带来性能的提升,但是用户首先需要知道严谨的事务标准,并在实际的生产应用中避免可能存在的潜在问题。对于InnoDB存储引擎而言,其默认的事务隔离级别为READ REPEATABLE
,完全遵循和满足事务的ACID特性。
事务四个典型特性,即ACID,原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
redo log
是实现持久性的关键,通过redo log
一直写入磁盘保证数据的持久性物理级别——只针对InnoDB
重做日志用来实现事务的持久性,即事务ACID中的D。其由两部分组成:一是内存中的重做日志缓冲(redo log buffer),其是易失的;二是重做日志文件(redo log file),其是持久的;而通过执行操作时一直往redo log buffer
中记录数据,然后再写入到redo log file
中去,使得数据库可以在数据丢失或系统崩溃时,通过重新执行日志中的操作来还原数据,从而保证数据的持久性。
因为在计算机操作系统中,用户空间(user space)下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间(kernel space)缓冲区(OS Buffer)。因此,redo log buffer
写入redo log file
实际上是先写入OS Buffer
,然后再通过系统调用fsync()
将其刷到redo log file中,因此redo log file
包括写入的数据和写入的位置。过程如下:
理想情况下,事务一提交就会进行刷盘操作,但是实际上是刷盘的时机是根据策略来决定的。
InnoDB
存储引擎为redo log
的刷盘策略提供了innodb_flush_log_at_trx_commit
参数,它支持三种策略:
redo log buffer
(缓存)中日志写入到os buffer
,而是每秒写入os buffer(page cache)
并调用fsync()写入到redo log file
(文件)中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,
redo log buffer
中的日志写入os buffer
并调用fsync()
刷到redo log file
中,
redo log buffer
写入os buffer
,然后是每秒调用fsync()
将os buffer
中的日志写入到redo log file,
这就是说如果mysql挂了,
每
1
秒写入是通过Innodb
存储引擎后台的一个线程进行实现的,他会每秒去把redo log buffer
中的内容写入到文件系统缓存os buffer
,然后调用fsync
刷盘
redo log是固定大小的,所以只能循环写,从头开始写,写到末尾就又回到开头,相当于一个环形。当日志写满了,就需要对旧的记录进行擦除,但在擦除之前,需要确保这些要被擦除记录对应在内存中的数据页都已经刷到磁盘中了。在redo log满了到擦除旧记录腾出新空间这段期间,是不能再接收新的更新请求,所以有可能会导致MySQL卡顿。(所以针对并发量大的系统,适当设置redo log的文件大小非常重要!!!)
存储表空间ID、页号、偏移量以及更新的值,所需的存储空间是很小的,刷盘快。
特点:
- redo日志是顺序写入磁盘的
- 在执行事物的过程中,每执行一条语句,就可能产生若千条redo日志,这些日志是按照产生的顺序写入磁盘的,也就是顺序IO,效率比直接写表的随机IO快,
- 事务执行过程中,redo log不断记录
- redo log跟bin log的区别,redo log是存储引擎层产生的,而bin log是数据库层产生的。假设一个事务,对表做10万行的记录插入,在这个过程中,一直不断的往redo log顺序记录,而bin
log不会记录,直到这个事务提交才一次写入bin log文件中。
逻辑级别——只针对InnoDB
重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK
语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改
当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作(也就是会把原来执行的操作撤销,而不是单纯的回退到之前那个时间节点去)。
事务提交后并不能马上删除undo log
及undo log
所在的页。这是因为可能还有其他事务需要通过undo log
来得到行记录之前的版本。故事务提交时将undo log
放入一个链表中,是否可以最终删除undo log及undo log
所在页由purge
线程来判断。
存放位置
undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undosegment)。undo段位于共享表空间内。可以通py_innodb_page_info.py工具来查看当前共享表空间中undo的数量。
insert undo log
update undo log
先了解MVCC之前我们得先知道事务的隔离级别,才好知道方便是如何解决事务的隔离性问题的!
在mysql中事务一共分为了四个隔离级别,并且分别可以依次排除掉不同的问题
隔离级别 | 允许脏读 | 允许不可重复读 | 允许幻影读 | 并发性 | 性能 |
---|---|---|---|---|---|
READ UNCOMMITTED(未提交读) | 是 | 是 | 是 | 高 | 高 |
READ COMMITTED(已提交读) | 否 | 是 | 是 | 中 | 中 |
REPEATABLE READ(可重复读) | 否 | 否 | 是(InnoDB不会出现) | 中 | 中 |
SERIALIZABLE(串行化) | 否 | 否 | 否 | 低 | 低 |
脏读:
不可重复读:
幻影读:
幻读是针对数据**插入(INSERT)**操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
解决方案:
InnoDB
中,把隔离级别设置为REPEATABLE READ
,也会解决掉幻影读的问题,在生成快照的基础上,事务会对读取的数据通过加锁的方式,以防止其他事务对这些行进行修改。这意味着当一个事务正在读取某一行时,其他事务不能修改该行,从而避免了幻读问题。Next-Key Locking
)的方式,对查询范围内的间隙(不存在的数据)进行锁定,以防止其他事务在该范围内插入新数据,从而避免了幻读。SERIALIZABLE
,则是每个事务对一个数据进行查询等操作时,其他的事务不会进行等待,等待该事务执行完后在执行,以串行的方式执行操作.在数据库中读取的方式主要有两种方式:
- 当前读:官方叫做Locking Reads(锁定读取),读取数据的最新版本. 常见的update/insert/delete、还有 select … for update、select … lock in share mode 都是当前读.
- 快照读:官方叫做 Consistent Nonlocking Reads(一致性非锁定读取), 也就是 MVCC 生成的 ReadView, 用于普通的 select 的语句.
MVCC的工作原理是使用数据在某个时间点的快照来实现的。这意味着,无论事务运行多长时间,都可以看到数据的一致视图,实现方式主要是通过ReadView 和undo log版本链实现。也意味着不同的事务可以在同一时间看到同一张表中的不同数据!如果你之前没有这方面的概念,这句话听起来可能有点让人迷惑,熟悉了以后你会发现还是很容易理解的
ReadView 的主要作用是为每个事务提供一致性的数据视图—-该视图存储的主要是:readview[m_ids], m_low_limit_id
格式存放,m_ids存放的是事务id的list集合
读以提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,说的就是ReadView,这也是可重复读和不可重复读的关键,可重复读是在事务开始执行第一次查询的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。
它主要包括:
**m_up_limit_id**
),是指 m_ids 里最小的值(当前未提交事务列表中的第一个);m_low_limit_id
):是指下一个要生成的事务 id。下一个要生成的事务 id 肯定比现在所有事务的 id 都大;对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:
undo log 版本链是基于 undo log 实现的。MySQL可从 undo log 日志中,读取到原插入、修改、删除之前的值,最终把值重新变回去,这就是回滚操作。
undo log 还包含两个隐藏字段 trx_id(事务id) 和 roll_pointer(回滚指针)。trx_id 表示当前这个事务的 id,MySQL 会为每个事务分配一个 id,这个 id 是递增的。roll_pointer 是一个指针,指向这个事务之前的 undo log。
undo log日志(只有在更新聚集索引记录时,才写undo log)
举例:每当一个事务修改了数据行,他执行的修改记录会先保持到undo buffer中,然后在持久化到磁盘的undo log文件中,直到该事务提交,并且该事务的undo log没有被别的事物引用到(当一个新的事物对该数据进行操作时就会生成一个快照引用)
- 多个日志会根据生成时间组成一条undo log链表
purge线程:
purge 线程是一个周期运行的垃圾收集线程, 对于没有事务引用的undo log进行清除, 但当purge线程发现undo log没有事务引用时将自动清除, 因为insert undo log在事务完成时直接删除, 所以需要清除的都是update undo log,
实现在InnoDB下的repeatable read隔离级别下ReadView 和undo log如何
说明:这步就是为了构建原始记录
begin;
insert into user(id,name) values(1,'张三');
commit;
begin;
update user set name = '李四' where id = 1;
commit;
begin;
-- 读到的name为张三
select * from user where id = 1;
https://zhuanlan.zhihu.com/p/190886874
https://blog.csdn.net/scm_2008/article/details/127985117
MySQL技术内幕:InnoDB存储引擎(第2版)_姜承尧