(注:
一致性非锁定读(select * from table):读已提交会读取当前记录的最新快照,因此依然存在不可重复读的问题;可重复读会读取当前记录在事务开始时的快照,因此可以避免不可重复读的问题,但是由于没有加锁,依然存在幻读的问题
一致性锁定读(select * from table for update):读已提交会添加record lock,因此可以避免不可重复读的问题,但是存在幻读;可重复读会添加next-key lock,因此可以避免不可重复读和幻读的问题
)
数据库的事务性包括ACID四个方面:
A:Atomic原子性,对于数据库的事务性操作要么全部成功,要么全部失败
C:consistency一致性
I: Isolation隔离性,并发情况下的数据库事务之间应该相互隔绝,互不影响
D:Duration持久性,数据库事务执行之后,数据就被持久化到硬盘
要实现数据库的事务,主要需要关注两个问题:事务性与并发处理。
事务性
1.为保证性能,数据库中执行过的操作指令再在commit之后不会马上持久化到磁盘中,但假如数据库挂掉,会有一部分未持久化的数据丢失,为了保证数据的前后一致,数据库有了redo log。redo log在每次commit之后会都持久化到磁盘中,因此能够保证数据库在宕机时的数据正确性。
2.数据库事务执行过程中如果需要回滚,或者为了支持mvcc功能,数据库有了undo log。
redo log和undo log
redo log,即重做日志,其作用为:当事务提交之后,记录修改的信息,保证事务的持久性。
mysql为了提升性能,不会在每次修改时都马上把数据同步到磁盘中,而是会维护一个缓冲池buffer pool,每次对数据库修改时,都会在缓冲池中缓存这次操作,而由一个后台线程做缓冲池与磁盘之间的同步。这就会导致一个潜在的问题:当缓冲池的信息没有被同步到磁盘,但是数据库却挂掉了时,缓冲池中的信息将会丢失。因此,为了保证这种情况下的数据一致性,数据库采用了redo log机制,
redo log机制包括redo log buffer和redo log两个模块,redo log buffer和上述的缓冲池一样处于内存当中,而redo log在磁盘中。事务中进行的修改操作都会被记录到redo log buf中,每次commit时,buffer中的内容都会持久化到磁盘中。因此。redo log会记录每一次成功操作的修改信息,并被持久化到磁盘中,当数据库挂掉时,系统会读取redo log恢复到最新数据。
redo log也会涉及到内存与磁盘之间的持久化,但是redo log的持久化性能优于数据,原因有两点:
1.redo log的持久化是顺序存储,数据库的缓存的持久化是随机存储;
2.缓存的同步以数据页为单位,每次传输的大小大于redo log;
总结:redo log的作用是为了保证已提交事务的一致性。
(注:数据库中还存在一种bin log,是用于数据库恢复全备文件以及主从复制的一种log。bin log和redo log看起来都是对数据进行备份,但是本质上存在很大的区别:
1.bin log是处于数据库上层的一种log,任何存储引擎对于数据库的修改都会产生bin log;而redo log仅针对innodb存储引擎;
2.两种log对于数据修改的内容形式不同,bin log记录的是执行的sql语句,如insert table ...,这是一种逻辑日志,不幂等,而redo log是记录的是对于每个页的修改结果,是一种物理日志,幂等;
3.redo log在事务执行中不断被写入,而bin log只在事务完成时进行一次写入)
undo log,即回滚日志,其作用与redo log相反,为:在事务提交之前,记录修改的信息,保证事务的持久性。当事务需要回滚时,undo log可以保证恢复到事务开始时的状态。
每条数据变更操作都会对应一条undo log记录的生成,且回滚日志必须先于数据持久化到磁盘上;当事务需要回滚时,系统会根据回滚日志做逆向操作,即insert的逆向操作为delete,delete的逆向操作为insert,update的逆向操作为update
另外,undo log也提供了对mvcc功能的支持:假如事务A锁定并修改了记录行a,在提交之前,事务B又尝试读取行a,为了避免不可重复读,数据库会通过undo log保证事务B读到被修改前的行a,实现了非锁定的读取。
总结:undo log的作用是为了保证未提交事务的原子性。
并发处理
即在多个事务同时对数据库进行操作时,各个事务之间不能有影响,各个事务之间需要存在隔离性。为了保证并发时的隔离性,数据库采用了锁技术与MVCC。
mvcc
mvcc,即多版本并发控制。
当事务A想要访问一条记录,但是这条记录被事务B暂时锁定时,事务A不会等待事务B解锁,而是会读取记录的一个快照版本,这种方式被称为一致性非锁定读,默认配置下,innodb是通过这种方式读取数据库数据的(可以通过配置,设置数据库读数据时必须加锁,这种方式被称为一致性锁定读)。而一个行记录可能会有多个快照数据,控制这些快照版本的技术即为mvcc。
总结:mvcc是innodb中控制数据读的方式。
mysql的锁技术
锁技术
mysql的锁分为读锁和写锁两种:
读写,即共享锁(S Lock),多个读请求可以共享一把锁,不会造成阻塞;
写锁,即排他锁(X Lock),会排斥其他所有获取锁的请求,直到本次请求完成并释放了锁。
根据锁定的范围可以分为三类:
Record Lock:锁定单条记录;
Gap Lock:锁定范围,但不包括记录本身;
Next-Key Lock:锁定范围+锁定记录本身,相当与record lock+gap lock。
并发情况下的读写问题
数据库在并发的情况下,可能会出现三种可能的读写问题:脏读/不可重复读/幻读
****脏读****
当前事务读取到其他事务未提交的修改后数据,读到脏数据。
****不可重复读****
当前事务读取到其他事务已提交的修改后数据。如:
事务1在两次读取的时候读到的结果存在差异,因为事务2在两次操作之间对字段做了修改。不可重复读的重点在于数据是否被修改。
****幻读****
当前事务对读取到的记录进行操作,但是读取之后操作之前这段时间内,该记录被其他事务删除或者新增了其他记录,导致后续的操作出错,这种现象叫做幻读
幻读的重点在于数据条目的删除或新增。
隔离级别及原理
数据库的并发处理与其隔离性相关,mysql存在四个隔离级别:读未提交/读已提交/可重复读/串行化
读未提交
read uncommited,读未提交可以提升并发处理性能,能做到读写并行,但是会存在脏读等问题。
读已提交
read commited,事务读取或者修改记录时加record lock,因此不会出现不可重复读,但是可能出现幻读。
可重复读
repeatable read,事务读取或者修改记录时加next-key lock,因此可以避免幻读。
串行化
serialization,原理为:将所有操作变为串行。
总结case
事务A正在修改记录a(这里将a理解为单条或多条记录),会首先对a进行加锁,然后修改数据。事务结束之前,事务B:
1.想要读取记录a,事务B会通过mvcc读取到记录a之前的一个快照数据,而不会等待事务A对a进行释放;
2.想要修改记录a,在读已提交隔离级别下,事务A的加锁为record lock,事务B修改失败;在可重复读的隔离级别下,事务A的加锁为next-key lock,事务B修改失败;
3.想要新增a相关的记录(如在两条数据之间插入),在读已提交隔离级别下,事务A的加锁为record lock,事务B修改成功;在可重复读的隔离级别下,事务A的加锁为next-key lock,事务B修改失败。