InnoDB引擎中的事务详解
作者:silmeweed
一、事务
事务(Transaction)是访问和更新数据库的程序执行单元;事务中可能包含一个或多个sql语句,这些语句要么都执行,要么都不执行。MySQL中默认采用的是自动提交(autocommit),在自动提交模式下,如果没有start transaction显式地开始一个事务,那么每个sql语句都会被当做一个事务执行提交操作。
start transaction; #1.开始事务 …… #一条或多条sql语句 commit; #2.提交事务
1.自动提交(autocommit)
可以关闭autocommit(set autocommit = 0);需要注意的是,autocommit参数是针对连接的,在一个连接中修改了参数,不会对其他连接产生影响。如果关闭了autocommit,则所有的sql语句都在一个事务中,直到执行了commit或rollback,该事务结束,同时开始了另外一个事务。
2. 特殊操作
在MySQL中,存在一些特殊的命令,如果在事务中执行了这些命令,会马上强制执行commit提交事务;如DDL语句(create table/drop table/alter/table)、lock tables语句等等。不过,常用的select、insert、update和delete命令,都不会强制提交事务。
二、事务的ACID特性
1.原子性(Atomicity):
1.1.定义
指的是整个事务要么全部成功,要么全部失败,如果事务中一个sql语句执行失败,则已执行的语句rollback也必须回滚,数据库退回到事务前的状态。
1.2.实现原理:undo log
InnoDB通过undolog保证rollback的时候能找到之前的数据。生成的undo log中会包含被修改行的主键(知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息。当事务回滚时能够撤销所有已经成功执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
2.一致性(Consistency):
2.1 定义:
一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态
2.2 实现原理
主要通过crash recovery和double write buffer的机制保证数据的一致性。
3.隔离性(Isolation):
3.1 定义:
隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。隔离是不同事务之间的相互影响。
(1)隔离级别:
读未提交在并发时会导致很多问题,而性能相对于其他隔离级别提高却很有限,因此使用较少。可串行化强制事务串行,并发效率很低,因此使用也较少。因此在大多数数据库系统中,默认的隔离级别是读已提交(如Oracle)或可重复读(InnoDB)(后文简称RR),可分全局隔离级别和本次会话的隔离级别。
(2)隔离性可以分为两个方面:
- (一个事务)写操作对(另一个事务)写操作的影响:锁机制保证 (写与写)隔离性
- (一个事务)写操作对(另一个事务)读操作的影响:MVCC保证 (写与读)隔离性
隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。行锁则只锁定需要操作的数据,但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。RR是无法避免幻读问题的,所以InnoDB引入MVVC实现的RR避免了幻读问题。
3.2脏读、不可重复读和幻读
(1)脏读:
当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。
(2)不可重复读:
在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
(3)幻读:
在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。
3.3 MVVC多版本的并发控制
MVCC的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本),事务A和事务C可以读取到不同版本的数据。
MVCC最大的优点是读不加锁,因此读写不冲突,多个版本的数据可以共存,主要基于以下技术及数据结构:
1)隐藏列:InnoDB中每行数据都有隐藏列,隐藏列中包含了本行数据的事务id、指向undo log的指针等。
2)基于undo log的版本链:前面说到每行数据的隐藏列中包含了指向undo log的指针,而每条undo log也会指向更早版本的undo log,从而形成一条版本链。
3)ReadView:指事务(记做事务A)在某一时刻给整个事务系统(trx_sys)打快照,之后再进行读操作时,会将读取到的数据中的事务id与trx_sys快照比较,从而判断数据对该ReadView是否可见,即对事务A是否可见。
trx_sys中的主要内容,以及判断可见性的方法如下:
- low_limit_id:表示生成ReadView时系统中应该分配给下一个事务的id。如果数据的事务id大于等于low_limit_id,则对该ReadView不可见。
- up_limit_id:表示生成ReadView时当前系统中活跃的读写事务中最小的事务id。如果数据的事务id小于up_limit_id,则对该ReadView可见。
- rw_trx_ids:表示生成ReadView时当前系统中活跃的读写事务的事务id列表。如果数据的事务id在low_limit_id和up_limit_id之间,则需要判断事务id是否在rw_trx_ids中:如果在,说明生成ReadView时事务仍在活跃中,因此数据对ReadView不可见;如果不在,说明生成ReadView时事务已经提交了,因此数据对ReadView可见。
3.4. 锁机制
两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB通过锁机制来保证这一点。锁机制的基本原理可以概括为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
行锁与表锁
表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。MyIsam只支持表锁,而InnoDB同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。
如何查看锁信息
有多种方法可以查看InnoDB中锁的情况,例如:
select * from information_schema.innodb_locks; #锁的概况 show engine innodb status; #InnoDB整体状态,其中包括锁的情况
下面来看一个例子:
#在事务A中执行: start transaction; update account SET balance = 1000 where id = 1; #在事务B中执行: start transaction; update account SET balance = 2000 where id = 1;
此时查看锁的情况:
show engine innodb status查看锁相关的部分:
通过上述命令可以查看事务24052和24053占用锁的情况;其中lock_type为RECORD,代表锁为行锁(记录锁);lock_mode为X,代表排它锁(写锁)。
4.持久性(Durability):
4.1 定义:
数据在事务commit 后在任何情况下都不能丢。InnoDB通过redolog保证已经commit的数据一定不会丢失。
4.2实现原理:redo log
redo log存在背景:
InnoDB Buffer Pool中包含了磁盘中部分数据页的映射,当向数据库写入数据时,会首先写入Buffer Pool。Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:
(1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。
到此这篇关于InnoDB引擎中的事务详解的文章就介绍到这了,更多相关InnoDB事务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!