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
发表回复