共计 4076 个字符,预计需要花费 11 分钟才能阅读完成。
1. ACID 模型
事务是一种操作数据的形式,一个事务能够是一条 SQL 语句,一组 SQL 语句或整个程序,满足以下特色:
- Atomic(原子性):事务中蕴含的操作被看做一个逻辑单元,要么都胜利,要么都失败
- Consistency(一致性):一致性指事务将数据库从统一状态转变为下一种统一的状态。在事务开始之前和事务完结当前,数据库的完整性束缚没有被毁坏。
- Isolation(隔离性):隔离不同事务,防止相互烦扰,保障所见即所得
- Durability(持久性):事务一旦提交就是永久性的。产生宕机等故障,数据库也能复原
2. InnoDB 的实现
InnoDB 和 ACID 模型:https://dev.mysql.com/doc/refman/5.6/en/mysql-acid.html
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 是须要进行随机读写的。
刷数到磁盘是在 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 笼罩,如:
- 事务 A 把状态改为 2,未 commit
- 事务 B 把状态改为 3,commit
- 事务 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 事务
1、2 实现但 3 没实现,就会导致主从不统一。InnoDB 通过先做 PREPARE 操作,接着再进行 bin log 和 redo log 的写入,如果宕机了,等复原后查看 uxid 是否曾经提交,没提交就从新提交,相当于做了最终统一。
5. 常见问题
Q1:耗时操作为什么不能在事务中进行?(事务长时间不提交会有什么问题?)
A1:事务应用锁来保障并发写操作之间的互斥,耗时操作相当于长事务,会把 mysql 线程阻塞,最终不可用
参考:
- 详细分析 MySQL 事务日志(redo log 和 undo log)
- Mysql 中的 MVCC
- 应用 CLion 编译调试 MySQL
- 《Mysql 技术底细:InnoDB 存储引擎》
- https://blog.csdn.net/u013235478/article/details/50625602
- https://www.jianshu.com/p/fdae2e30b9fa?from=timeline