Mysql 学习笔记:InnoDB 事务和 ACID 模型

1. ACID模型

事务是一种操做数据的方式,一个事务能够是一条SQL语句,一组SQL语句或整个程序,知足如下特征:html

  • Atomic(原子性):事务中包含的操做被看作一个逻辑单元,要么都成功,要么都失败
  • Consistency(一致性):一致性指事务将数据库从一致状态转变为下一种一致的状态。在事务开始以前和事务结束之后,数据库的完整性约束没有被破坏。
  • Isolation(隔离性):隔离不一样事务,避免互相干扰,保障所见即所得
  • Durability(持久性):事务一旦提交就是永久性的。发生宕机等故障,数据库也能恢复

2. InnoDB的实现

InnoDB和ACID模型:https://dev.mysql.com/doc/refman/5.6/en/mysql-acid.htmlmysql

2.1 redo & undo


redo log称为重作日志,是物理日志,记录的是磁盘页的修改操做。redo log包括两部分:一是内存中的日志缓冲(redo log buffer),该部分日志是易失性的;二是磁盘上的重作日志文件(redo log file),该部分日志是持久的。undo log是逻辑日志,记录的是数据行记录。undo log也包含两部分:undo log buffer、undo log。算法

2.2 持久性


事务一旦提交操做成功,该事务所作的更改就不会受到电源故障、系统崩溃等问题影响。持久性一般涉及到对磁盘存储的写入,并具备必定数量的冗余,以防止写入操做期间出现电源故障或软件崩溃。

InnoDB经过Force Log at Commit机制实现事务的持久性,即当事务提交时,必须先将该事务的全部日志写入到重作日志文件进行持久化,待事务的commit操做完成才算完成。在InnoDB存储引擎中,由两部分组 成,即redo log和undo log。redo log用来保证事务的持久性,undo log用来帮助事务回滚及MVCC的功能。redo log基本上都是顺序写的,在数据库运行时不须要对redo log的文件进行读取操做。而undo log是须要进行随机读写的。sql

刷数到磁盘是在commit时发生的,有3中不一样到策略,经过innodb_flush_log_at_trx_commit参数控制:数据库

master thread会每秒把redo log buffer和undo log buffer刷新到磁盘中,即便没有commit,这就是为何即便是长事务,commit操做也很快的缘由。因此设置为0时,实际是没有额外操做。2比1要安全,由于1是把数据写入用户空间,mysql服务挂了数据就丢了,2会把数据写入系统空间,只会在服务器宕机是发生数据丢失。缓存

虽然用户能够经过设置参数innodb_flush_log_at_trx_commit为0或2来提 高事务提交的性能,可是须要牢记的是,这种设置方法丧失了事务的ACID特性。安全

2.3 隔离性


隔离性要解决的几个问题:服务器

1.脏读并发

事务A对缓冲池中的数据作了修改而且尚未被提交(commit),这时被另一个事务B读取到了数据,由于查询是优先走缓存的。

2.不可重复读分布式

事务A中对同一行数据屡次读取,若是在这期间事务B对数据进行了修改,那么事务A会读取到提交过的数据,形成了不一致。

3.幻读

可重复读要求对相同数据屡次查询结果要一致,显然幻读并不属于不可重复读,对幻读的解决是在serializable级别中,可是InnoDB在RR级别就解决了幻读问题,但愿不要把这两个概念搞混。

  • 事务A查询orderid=1 and status=1的记录,发现记录不存在操做insert
  • 事务B插入orderid=1 and status=1的记录,commit
  • 事务A commit
  • 数据重复插入

4.丢失更新

丢失更新体现逻辑上,事务A的更新操做会被事务B覆盖,如:

  1. 事务A把状态改成2,未commit
  2. 事务B把状态改成3,commit
  3. 事务A commit,应用程序继续执行后续操做

这时就发生了逻辑错误,即:当前状态没有改成2,不符合逻辑预期。

ANSI/ISO SQL标准定义了4中事务隔离级别

下面解读一下RC和RR级别

2.3.1 RC 解决脏读

RC级别主要为了解决脏读,即:不能读取到未提交的数据。经过MVCC来实现,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操做没有冲突” 这么一个概念。

脏读的缘由是事务提交以前对数据的变动会更新缓冲池中的data page,select会直接查询index page和data page,因此能查询到。如何隔离呢?1.锁定读,在修改时进行读取会被阻塞 2.非锁定读。为了提高并发性能在RC和RR级别中使用的是非锁定读,经过读取undo log多个版本的快照数据实现隔离。在RC中老是读取被锁定行的最新一份快照数据(快照读),由于总能读取到最新数据因此不可重复读。

2.3.2 RR 解决不可重复读和幻读

RR级别主要解决2个问题:1.不可重复读 2.幻读

不可重复读

可重复读核心要实现的是在当前事务执行过程当中对相同数据的屡次查询结果要一致,其它事务可继续修改数据。经过读取事务开始时的行数据版本就能实现了(当前读),上面提到过版本是经过undo log来实现的。

幻读

在RR中经过加锁来解决幻读问题:Next-Key Lock,包含Gap Lock + Record Lock。若是索引中含有惟一索引,则降级为Record Lock,这样只会锁定索引自己不会出现范围锁。

若是插入order_id=2的也会被锁定,缘由是由于我在建立表时没有对order_id加索引,看下加了以后的效果:alter table record add index order_id_status (order_id,status) ;

若是不建立组合索引会分别对order_id和status作范围锁,这样基本和锁表没什么区别了,使用起来挺危险的。因此若是要用锁必定要确认锁的范围,最好使用主键或组合索引来缩小锁定范围。

2.4 原子性

事务的原子性保障一组操做要么成功要么失败,经过redo log和undo log实现。为何要这么作的?由于数据库事务的原子性比操做系统的原子性状况要复杂,存在失败的可能,好比插入重复的主键就会报错,事务不能继续执行,须要回滚保证原子性。

以转帐为例:A转帐100元给B,须要保证A和B的帐户余额一块儿完成减小和增长。

2.5 一致性

提到一致性估计立马想起来的就是分布式系统中不一样数据副本之间的数据一致性问题了,而ACID中的一致性是指必须使数据库从一个一致性状态变到另外一个一致性状态,如何理解?

  • 知足约束:类型一致、not null、惟一值
  • 运算结果一致:a = 100;begin ... set a = a-100; ... set a = a-100; commit; a应该等于-100。

在Mysql中除了上述数据完整性约束和运算一致性以外,还存在数据一致性问题,前面不是刚提到ACID一致性不是指数据一致性吗?这是由于在Mysql中存在多个数据副本,如:Mysql InnoDB DML操做会先写入buffer,这样数据就存在缓存和磁盘两个地方,除了定时把缓存数据写入磁盘,还使用doublewrite buffer和崩溃恢复机制减小数据丢失致使的数据不一致问题。

若是开启了bin log还使用了内部XA事务解决bin log和redo log之间的数据一致性,这些都是Mysql中的一致性相关问题。同时还提供了锁机制解决在并发场景下的丢失更新致使数据和预期不一致问题。

3.锁

单靠事务并不能解决对共享资源的并发操做带来的互相干扰,这须要经过锁来解决。InnoDB实现了两种标准的行级锁:

  • 共享锁(S Lock),容许事务读一行数据。
  • 排他锁(X Lock),容许事务删除或更新一行数据。

3.1 锁的类型

3.1.1 一致性非锁定读

因为S锁和X锁是不兼容的,若是读取的行当前加了X锁,那么读取不会等待锁释放,会去读取行快照。在InnoDB存储引擎经过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据的操做叫作“非锁定读”。非锁定读经过undo log来实现,若读取的记录被其它事务占用,当前事务能够经过undo读取以前的行版本信息(须要进行还原获得以前的数据,和rollback同样)。

3.1.2 一致性锁定读

在RR隔离级别下select操做使用非锁定读,可是某些状况下用户须要显示的对读取加锁来保证数据的一致性,如:并发场景下,请求1把订单从2改成3,请求2把订单状态从2改成4,这样就会出现状态被修改了2次会形成bug。这时就须要对读取进行锁定,锁定读是对select语句加锁:

  • select for update 对行加X锁
  • select lock in share mode 对行加S锁

符合咱们预期的应该只有for update

3.2 锁的算法

  • Record Lock:单个行记录上的锁。经过锁主键索引实现行锁。
  • Gap Lock:间隙锁,锁定一个范围,但不包含记录自己。经过锁非汇集索引实现范围锁。
  • Next-Key Lock:Gap Lock+Record Lock,锁定一个范围,而且锁定记录自己。

4. 内部XA事务

一、2完成但3没完成,就会致使主从不一致。InnoDB经过先作PREPARE操做,接着再进行bin log和redo log的写入,若是宕机了,等恢复后检查uxid是否已经提交,没提交就从新提交,至关于作了最终一致。

5. 常见问题

Q1:耗时操做为何不能在事务中进行?(事务长时间不提交会有什么问题?)

A1:事务使用锁来保证并发写操做之间的互斥,耗时操做至关于长事务,会把mysql线程阻塞,最终不可用

参考:

相关文章
相关标签/搜索