Mysql的innodb存储引擎是通过事务来保证数据的一致性的

数据库事务通常包含了一个序列的对数据库的读/写操作包含有以下两个目的

为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使再异常状态下仍能保持一致性的方法

当多个应用程序再并发访问数据库时,可以再这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰

 

特性:

事务的特性有原子性,隔离性,持久性,一致性,其实,原子性,隔离性,持久性都是为了保证一致性

 

原子性:原子性是指多个操作都是不可分割的,智能全部执行成功,或者全部执行失败。Mysql的原子性通过undo log来实现,undolog时innodb存储引擎特有的,具体的实现方式是:将所有对数据的修改(增删改)都写入日志(undo log),如果一个事务中的一部分操作已经成功,但是另一部分操作由于断电,系统崩溃等原因无法成功操作,则通过回溯日志将已经执行成功的操作撤销,从而达到全部失败的目的。 undo log是逻辑日志,可以理解为记录和事务相反的sql语句,事务执行insert语句,undo就记录delete语句,它可以追加写的方式记录日志,不会覆盖之前的日志,除此之外,undo log还用来实现数据库多版本并发控制(mvcc)

 

持久性:持久性是指,一个事务对数据的所有修改都会永久的保存再数据库中,mysql的持久性是通过redo log来实现的,redolog也是innodb特有的,具体实现方式是当发生数据修改时,innodb会将记录首先写道redo log,并更新内存,此时更新就算完成了,同时innodb会在合适的时机将记录刷新到磁盘。redo log时物理日志,记录的是再某个数据页做了什么修改,而不是以sql的形式记录,它有固定大小,是循环写的方式记录日志,空间用完后会覆盖之前的日志。undolog和redolog并不是直接写道磁盘的,而是先写入logbuffer,再等待合适的时机同步到osbuffer,再由操作系统决定合适刷到磁盘。    既然undolog和redolog都是从logbuffer到osbuffer,再到磁盘,所以中途还是有可能因为断电/硬件故障等原因导致日志丢失,为此mysql提供了三种持久化方式,通过innodb_flush_log_at_trx_commit来控制,这个参数由三个值0 , 1 ,2 ,这三个值的意思分别是:

0:提交后写入logbuffer ,每秒写入osbufer并调用fsync()刷新到磁盘

1(默认):每次提交后直接写入osbuffer,并且调用系统函数fsync()把日志写道磁盘,就保证数据的一致性来说,这种方式是最安全的,但是安全就意味着效率低下,每次都直接写到osbuffer并写道磁盘,无疑会导致单位时间内io的次数过多而导致效率低下

2:每次提交写入osbuffer,每秒调用fsync刷新到磁盘。

那么出现异常后,数据库重启会首先读取redo日志,把成功提交但是还没来得及写入磁盘的数据重新写入磁盘,保证了持久性,再读取undo log将还没有成功提交的事务进行回滚,保证了原子性

隔离性:数据库事务的隔离性是指:多个事务并发执行时一个事务的执行不影响其他事务的执行;数据库的隔离级别有:未提交读,已提交读,可重复读,序列化;

可重复读时mysql的默认隔离级别,比如事务1执行select 查询出一条数据,然后事务2 执行了插入操作,这时事务1再次查询就可能会出现查询出两条数据的,就出现了幻读。但是我们实际操作下发现和预期的结果并不一致,没有出现幻读,实际上,mysql再可重复读的隔离级别下用MVCC解决了select普通查询的幻读现象,具体实现是:事务开始时,第一条select语句查询的结果集会生成一个快照,并且这个事务结束前,同样的select语句返回的都是这个快照的结果,而不是最新的查询结果,这就是mysql可重复读隔离级别对普通select查询使用的快照读

快照读和mvcc有什么关系;mvcc是多版本并发控制,快照就是其中的一个版本,具体实现方式是mysql会给每个表自动创建三个隐藏列:DB_TRX_ID 事务id, DB_RLL_PTR 回滚指针, DB_ROW_ID 隐藏id ,由于undolog中记录了各个版本的数据,并且通过DB_ROLL_PTR可以找到各个历史版本,并且通过db_trx_id决定使用哪个版本(快照)所以相当于是undolog实现了mvcc,mvcc实现了快照i读。

那么如此看来,mysql的可重复读利用快照读已经解决了幻读问题?但是事实上并非如此,如果第二次查询前事务2插入一条数据insert into student(name) values('wangwu'),然后事务1进行一次更新:update student set name = 'zhaoliu' where name = 'wangwu' ,之后的查询会发现还是只有两条一跳数据,但是如果我们看的话会发现事务2插入的数据的名字编程了wangwu;

这其实是mysql对insert,update和delete语句使用的是当前读,因为涉及到数据的修改,所以mysql必须拿到最新数据才能修改。

那么再可重复读下是怎么解决幻读的呢,

间隙锁:我们都知道innodb支持行锁,并且行锁是锁住索引,而间隙锁是锁定索引记录间隙,确保索引记录的间隙不变,间隙锁是为可重复读或以上级别设计的,间隙锁和行锁一起组成了nextkey lock当,innodb扫描索引记录的时候,会首先对索引记录加上行锁,再对索引记录两边的间隙加上间隙锁,加上间隙锁之后,其他事务就不能再这个间隙插入记录,这样就有效防止了幻读的发生。

默认情况下,innodb工作在可重复读级别下,并且以next-key lock的方式对索引进行加锁,当查询的索引具有唯一性的时候,innodb会对next-key lock进行优化,将其降为行锁,仅仅锁住索引本身,而不是范围,若是普通索引,则会使用next-key lock将记录和间隙一起锁定

使用快照读的语句
select * from ...
1
使用当前读的语句
select * from ... lock in share mode
select * from ... for update
insert into table...
update table set ...
delete table where ...