重做的含义就是根据redo log重新做一遍数据更新。
当对数据库在内存种执行修改操作成功后,并不是直接将内存种的数据页刷到磁盘上,而是将修改信息(哪个数据页哪里发生了修改)写入redo buffer,redo buffer再根据三种策略中的一种顺序写入磁盘中的redo log中,随后由mysql服务选择合适时机根据redo log记录,将新数据刷入磁盘中。
在关系数据库中,这种先预写日志后面再将数据刷盘的机制,称为WAL(Write-ahead logging),翻译成中文就是预写式日志。
redo log保证了
undo的中文意思是 不做了,其含义就是已经执行过的操作不做了,回滚到做之前的状态。
InnoDB存储引擎在实际进行增、删、改一条记录时,都需要先把对应的undo日志记下来。一般每对一条记录做一次改动,就对应着一条undo日志。当事务错误或者rollback,mysql会利用undo log中备份的数据恢复到事务开始之前的状态。
同 redo log类似,添加undo log日志记录并不是直接写入undo log日志,而是先写入 undo buffer中,然后再写入undo log中。
事务是逻辑上的一组不可分割的数据库操作序列,要么都执行,要么都不执行。事务也是数据库并发控制的基本单位。事务的定义同时也是事务的存在理由,即将一系列的数据操作绑定成一个整体统一管理。其典型应用场景是银行不同账户之间的转账。
张三账户现有1000元,李四账户现有500元,此时是一种状态A。
某天,张三账户向李四账户转账300元
若成功则张三账户减少300元,余额为700元;李四账户增加300元,余额为800元。此时是一种状态B。
若失败,则回滚操作,使张三账户仍旧是1000元,李四账户仍旧是500元。此时是一种状态A。
以上转账过程可以看到,张三和李四的状态要么是A要么是B,不可能存在其他过渡状态,这就是事务所发挥的作用,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。
特别注意的是,MySQL的 InnoDB 引擎提供事务和行锁,下文讨论的事务内容基于InnoDB引擎。
事务具备以下四种特性
操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
当对数据库进行并发访问时,会产生四?????种并发问题。数据库引擎为了解决这些问题,保证在并发操作过程中数据一致性,就引入了隔离性的概念,即隔离性的本质作用是控制并发访问数据库。其思想同java的并发编程框架类似。
其定义为数据库允许多个并发事务同时对相同数据进行的读写的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致
当事务并发操作时,若不采取任何措施,可能出现以下两类五种问题
两类指
写-写
一个事务中的写操作对另一个事务中的写操作的影响,包括1)第一类丢失更新 2)第二类丢失更新
写-读
一个事务中的写操作对另一个事务中的读操作的影响,包括1)脏读 2)不可重复读 3)幻读
事务并发的问题是在于各个事务只关注自己的操作链,没有关注在自己事务的操作期间,其他事务对数据的修改操作,造成自己的数据已经无效。
其实例入图,
站在上帝视角可以发现,A事务的回滚操作覆盖了B事务的更新操作,使B事务更新丢失。
同第一类丢失更新类似,只是事务的回滚变成了修改后提交。
A事务的修改提交操作覆盖了B事务的修改提交,导致B事务的更新丢失。
脏读是指事务读取到其他事务未提交的数据。
未提交数据可认为是临时数据,临时数据有可能是最终数据(提交),也有可能不是最终数据(回滚)。当临时数据等于最终数据时,没有问题。当临时数据不等于最终数据时,导致出现脏读问题。
脏读过程如图所示,
在S4阶段,由于B事务的回滚,导致A事务读取的name值是脏数据,即所谓脏读。
不可重复读是指在同一次事务中前后查询不一致的问题。
不可重复读出错情况如下图
在以上过程中,A事务并未修改id=1的操作,但前后两次读取该记录的值不一致,即所谓的不可重复读。
事务 A 根据条件查询得到了 N 条数据,但此时事务 B 删除或者增加了 M 条符合事务 A 查询条件的数据,且B事务已提交完成,真实的数据集已经发生了变化。但事务 A 再次进行查询的时候,却查询不出来这种变化,产生了幻读现象。
以新增数据为例(删除类似),幻读现象如下图所示。
S1阶段: A事务和B事务开启
S2阶段
A事务查询stu表,查出一条数据;
B事务执行插入一条数据;
S3阶段
A事务执行其他操作;
B事务提交,“李四”数据插入数据库中。
S4-S8阶段
A事务执行其他操作;
在A事务之外对数据库进行查询(同A事务在S2阶段的查询条件完全相同),可以查到两条数据;
S9阶段:A事务执行查询(同A事务在S2阶段的查询条件完全相同),只有一条数据,没有查id=2的数据;
在S9阶段出现幻读现象,即数据库已经添加了新数据,A事务中却查询不到新数据。对于事务A来说,它认为当前数据库的最大id为1,那么若在A事务中的S10阶段执行插入已一条记录,该记录的id=2。由于数据库id=2的记录真实存在,因此A事务必然无法成功完成插入操作。A事务就会感觉到幻觉,明明数据库中没有id=2的记录,但自己插入id=2的记录却不成功。
出现幻读的原因是数据库引擎为了保证在同一个事务中的可重复读(即mysql默认的隔离级别为REPEATABLE_READ (可重复读),该级别保证了在同一个事务中,同一个查询语句的查询结果一致。),在开启事务时刻,将该事务绑定数据库在此刻的状态,使该事务中的查询操作屏蔽了后续时刻数据库的变化。如上图中,A事务绑定了数据库在S1阶段的状态,虽然在S1阶段之后的S3阶段B事务的提交操作改变了数据库的状态,但A事务感知不到数据库的状态变化,进而造成了幻读。
特别注意的是,不可重复读强调同一条记录的修改操作。在A事务中对同一条数据记录的修改操作,造成在B事务中该修改前后的相同查询结果不一致。幻读强调数据库中记录的增删操作,在A事务中对数据库记录进行增删操作,在B事务中该操作的后面无法感知到增删变化。
为了解决事务并发的问题,SQL定义组织提出了四种隔离级别。这四种隔离级别可以解决事务并发中的问题。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
未提交读(RU,read-uncommitted) | 存在 | 存在 | 存在 |
提交读(RC,read-committed) | 不存在 | 存在 | 存在 |
可重复读(RR,repeatable-read) | 不存在 | 不存在 | 存在 |
串行化(serializable) | 不存在 | 不存在 | 不存在 |
每一个数据库厂商都有自己实现这四种隔离级别的方式,mysql的innodb引擎使用锁机制和mvcc实现了四种隔离级别。
一个事务一旦提交,它对数据库中数据的改变是永久性的,不会因为宕机等故障导致数据丢失。
为了提高数据库的读写效率,InnoDB使用Buffer Pool作为操作系统和磁盘之间的缓冲,BufferPool位于系统内存中,包含了磁盘中部分数据页的映射。当当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
由于BufferPool位于内存中,当Mysql宕机时,若BuferPool中的数据尚未写入磁盘,则导致数据丢失,无法保证事务的持久性,于是引入redo log解决该问题。
当事务提交时,mysql首先将操作记录在redo log中,然后再写入BufferPool。由于redo log位于磁盘中,不会因mysql宕机而消失,因此当mysql重启之后,可以从磁盘中的redo log读取记录,将数据写入磁盘,满足了持久性要求。
事务的执行使数据从一个状态转换为另一个状态,整个数据的完整性保持稳定。
总的来说,一致性是目的,是一种结果。实现一致性需要在两方面做工作
在数据库层面
事务的AID特性保证了数据具备一致性。这句话的意思是只要实现数据库的AID特性,那么数据库中的数据一定是正确的。
这个层面由数据库引擎保证,用户不用做任何操作。
在应用层面
基于数据库的AID特性,同时需要业务代码遵循正确的业务逻辑,实现业务数据的一致性。这就调调了业务代码逻辑必须正确。例如在张三给李四转账的例子中,代码将张三的钱减少了,但是代码没有给李四账户加钱,那肯定无法无法保证金额的一致性。
这个层面需要用户在代码实现正确的一致性逻辑。
原子性,隔离性和持久性是数据库的属性,而一致性是应用程序的属性。只有保证了事务的持久性、原子性、隔离性之后,一致性才能得到保障。也就是说 A、I、D 是手段,C 是目的。
解决并发问题的常用手段包括悲观锁和乐观锁。
悲观锁是一种解决并发问题的思想,它悲观的认为有其他的操作者,因此在自己开始阶段就锁定全部目标资源。在mysql中,悲观锁主要在select中使用(在其后加上 for update),其实现原理基于mysql的锁机制。悲观锁数
乐观锁也是一种解决并发问题的思想,它乐观的认为没有其他操作者,因此只在操作的最后阶段检查是否有其他操作者。在mysql中,乐观锁主要是通过添加版本字段+业务代码CAS控制实现对其的使用,乐观锁没有底层实现原理。