前面具体讲了MySQL中的锁实现的方式,解释了是如何保证数据在并发状况下的可靠性,并提到了事务REPETABLE READ和READ COMMITTED,解释了一下这两种事务的不一样。本章讲具体就事务的实现过程进行记录,扫除这块让人疑惑的知识点。mysql
事务是数据库区别于文件系统的一个重要特性之一。文件系统中,若是写文件时系统崩溃了,可能文件就被损坏了。虽然有机制将一个文件回退到某个时间点,但对于文件同步问题就无能为力了。事务会把数据从一种一致性状态转换成另外一种一致性状态。数据库提交时,要不都成功了,要不都失败了。算法
InnoDB的事务符合ACID的特性:sql
原子性 atomicity数据库
一致性 consistency数组
隔离性 isolation缓存
持久性 durability服务器
前一章的锁主要是对事务的隔离性进行了说明,本节主要关注事务的原子性这个概念。网络
事务是访问并更新数据库中数据的一个程序执行单元,能够由1到多条SQL组成。在一个事务中,要不全作,要不不作。能够把事务看作是办一件事,有不少个步骤,只要事情搞砸了,全部的步骤都不被承认,比如没干。数据结构
事务有很严格的定义,必须知足ACID特性,可是因为各类缘由,有些时候并无知足全部的标准,好比以前提到的READ COMMITTED,其就不知足隔离性要求。大部分状况,这样不会形成严重的后果,反而会有性能的提高,因此具体使用哪一种事务,给了人们自由选择的能力。架构
原子性:就是要不不作,要不全作。好比转帐,先扣本身帐户的钱,再往其余人帐户上加一笔钱。这个过程是要保证原子性的,否则扣成功了,却转失败了,后果是灾难性的。原子就意味着不可分割,失败了就要还原到最初的状态。
一致性:一致性指事务将数据库从一种状态转变成下一种一致的状态。在事务开始以前,结束以后,数据库的完整性约束没有被破坏。好比一个字段是惟一索引,对其进行了修改,事务提交或事务操做后变得非惟一了,这就破坏了一致性要求。
隔离性:要求每一个读写事务的对象对其余事务的操做能相互分离,即每一个事务当前看不到其余事务的操做。
持久性:事务一旦提交,结果就是永久性的。磁盘损坏之类的不是该考虑范围。
从事务理论的角度来讲,能够把事务分为如下几种类型:
扁平事务(Flat Transactions)
带有保存点的扁平事务(Flat Transactions with Savepoints)
链事务(Chained Transactions)
嵌套事务(Nested Transactions)
分布式事务(Distributed Transactions)
扁平事务:
最简单,使用频繁。全部操做都是处于同一层次,由BEGIN WORK开始,由COMMIT WORK 或ROLLBACK WORK结束,操做是原子的。成功完成的占96%,应用程序要求中止的3%,强制终止的为1%。这个是结果的占用百分比。
扁平事务的主要限制就是不能部分回滚。好比定机票:定了xx->yy->zz的机票,yy->zz的机票预约失败,连xx->yy的都会回滚,显然有些时候这并非咱们但愿的。
带有保存点的扁平事务:
这个就是解决上面提到的问题,给了一个保存点,后续失败后能够回到这个保存点。保存点使用SAVE WORK函数建立,出现问题,可以做为内部的重启动点,根据应用逻辑能够选择回到哪个保存点。
ROLLBACK WORK : 保存点。
链事务:
能够当作是保存点模式的一种变种。前一种在系统崩溃时,全部保存点都会消失,由于其非持久,事务恢复时要从新执行。
链事务的思想是:提交一个事务时,释放不须要的数据对象,将必要的处理上下文隐式地传给下一个要开始地事务。提交事务和下一个事务开始操做是一个原子操做。这就意味着下一个事务将看到上一个事务地结果,在那个事务中执行同样。
经过上述描述,能够看出这个与带有保存点的扁平事务不一样之处在于:只能回滚到上一个节点,由于以前的已经commit了。第二个就是因为以前commit了,因此以前的锁被释放了。
嵌套事务:
这是一个层次结构框架。由一个顶层事务控制各个层次的事务。下面嵌套的事务被称为子事务,控制每个局部的变换。
嵌套事务是一个树,子树能够是嵌套事务,也能够是扁平事务。叶子节点是扁平事务,根节点的是顶层事务,其余的是子事务,前驱是父事务,后继是儿子事务。子事务能够提交和回滚,可是不会马上生效,除非其父事务已经提交了。全部,全部的子事务都在顶层事务提交以后才真正提交。任意事务回滚都会致使其子事务回滚,因此子事务只有ACI特性,没有D特性。
在Moss理论中,实际的工做是交给叶子节点完成的,高层事务负责逻辑控制,决定什么时候调用相关子事务。即便一个系统不支持嵌套事务,也能够经过保存点来模拟嵌套事务。可是用户没法选择哪些锁被子事务继承,哪些须要被父事务保留。保存点中全部的锁都可以被访问和获得。可是嵌套事务中不同,好比父事务P1,持有对象X和Y的排他锁,调用一个子事务P11,父事务能够决定传递哪些锁,若是P11还有z锁,父事务能够反向继承,持有X、Y、Z的排他锁。调用子事务P12,能够选择持有哪些锁。
想要实现事务的并行性,就须要真正支持嵌套事务了,保存点是不行的。
分布式事务:
一般是一个在分布式环境下运行的扁平事务,所以须要根据数据所在位置访问网络中的不一样节点。好比一个事务,涉及到多个数据库的增删,其中有一个出错,全部的数据库实例都要作出相应的反应才行。
InnoDB引擎支持扁平事务,带有保存点的事务,链事务和分布式事务。不支持嵌套事务,可是能够试着用带有保存点的事务进行模拟串行的嵌套事务。
以前提到了redo页和undo页,这里详细讲一下这些知识。
redo log称为重作日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。
或许会认为undo是redo的逆过程,可是不是。redo和undo的做用都是一种恢复操做,redo恢复提交事务修改的页操做,undo是回滚记录到某个特定版本。所以二者记录的内容不一样,redo是物理日志,记录的是页的物理修改操做。undo是逻辑日志,记录每行的记录。
重作日志用于实现事务的持久性,由两部分组成:一是内存中的重作日志缓冲,二是重作日志文件。
采起Force Log at commit机制实现事务的持久性。即当事务提交时,必须先将事务的日志写入重作日志文件进行持久化,以后才进行commit,最后才算完成。重作日志在InnoDB中由两部分组成:redo log和undo log。redo log保证事务的持久性,undo log帮助事务回滚及MVCC的功能。redo log基本是顺序写的,运行时不须要对redo log文件进行读取操做。undo log是须要随机读写的。
重作日志打开并无使用O_DIRECT选项,因此会先写入文件系统缓存,要确保持久化了,必须调用fsync操做,这个操做的性能取决于磁盘,因此事务的性能由磁盘决定。
InnoDB容许用户不强制事务调用fsync操做,而是由最初提到的线程在一个周期中执行fsync操做。这个会提升性能,同时带来了宕机时丢失事务的风险。
innodb_flush_log_at_trx_commit用来控制重作日志刷新到磁盘的策略。默认为1,表示提交事务就执行一次fsync操做。0表示事务提交时不进行重作日志操做,这个操做在master thread中完成,其每秒进行一次fsync操做。2表示事务提交时将重作日志写入文件,但只写入文件系统缓存,不进行fsync操做。宕机时会丢失未从文件系统缓存刷入磁盘的那部分数据。50万行数据在0模式可能须要13秒,1模式2分钟,2模式23秒。因此0和2模式能够提升性能,可是事务的ACID特性就没法保证。
以前说过MySQL中的二进制日志文件,其用来进行POINT-IN-TIME的恢复和主从复制环境的创建。表面上和重作日志类似,都是记录了数据库的操做日志,可是二者有很大的不一样。重作日志是InnoDB产生的,二进制日志是MySQL上层产生的。任何存储引擎的更改都会产生二进制日志。其次,两种日志的记录内容不一样,二进制是一种逻辑日志,记录的是对应的SQL语句,InnoDB的重作日志是物理格式日志,记录的是对每一个页的修改。此外,二进制日志是在事务提交完成后一次写入,重作日志是在进行中不断被写入。
InnoDB中重作日志是512字节进行存储的,这意味着重作日志都是以块的方式进行保存的,称之为重作日志块,每块大小512字节。若一个页中产生的重作日志大于512字节,那么须要分割成多个重作日志块进行存储,由于是512字节,因此日志的写入能够保证原子性,不须要doublewrite技术。
重作日志包含日志块头及块尾,固然还有日志自己。日志头一共12字节,尾8个字节,实际可存储大小是492字节。
log block header:
LOG_BLOCK_HDR_NO 4字节
LOG_BLOCK_HDR_DATA_LEN 2字节
LOG_BLOCK_FIRST_REC_GROUP 2字节
LOG_BLOCK_CHECKPOINT_NO 4字节
log buffer好似一个数组,NO就是用来标记数组的位置,递增其循环使用。第一位用来判断flush bit,因此最大值是2^31。
LEN代表log block所占用的大小,最大就是0x200,512字节占满了。
REC_GROUP表示log block中第一个日志的偏移量,若是和LEN相同,表面当前log block不包含新的日志。好比第一个事务使用了500字节,超过了492的最大值,而第二个事务使用了100字节,因此就会有两个block,1事务的尾部和2事务的数据在一个block中。因此第二个block的偏移量为8+12。
CHECKPOINT_NO表示最后写入时的检查点4字节的值。
log block tailer就一个部分,和LOG_BLOCK_HDR_NO相同。
log group是重作日志组,有多个重作日志文件。InnoDB源码中已支持log group镜像功能,可是在ha_innobase.cc文件中禁止了该功能。所以InnoDB存储引擎只有一个log group。
log group是一个概念,没有实际存储的物理文件。由多个重作日志文件组成,每一个日志文件大小是相同的,在InnoDB 1.2以前,重作日志大小小于4GB,1.2版本提高了大小的限制为512GB。1.1版本就支持大于4GB的重作日志。
存储引擎运行过程当中,log buffer是按必定的规则将内存中的log block刷新到磁盘:
事务提交时,log buffer的一半空间被使用,log checkpoint时。
对于log block的写入追加在redo log file的最后部分,当一个redo log file被写满时,会写入下一个redo log file,使用的方式是round-robin。
虽然log block老是追加在redo log file的最后,可是写入并不都是顺序的,由于除了保存了log block,还保存了一些其余信息,一共占2kb大小,即每一个redo log file的前2kb都不保存log block的信息。对于log group的第一个redo log file,其前2kb的部分保存了4个512字节大小的块。
log file header 512
checkpoint1 512
空 512
checkpoint2 512
上述信息只在每一个log group的第一个file中保存。其他file 保留空间,但不保存信息。因为这些信息,致使写入不是顺序的,由于须要更新这2KB的数据,这些信息对于InnoDB的恢复操做十分重要。后面的checkpoint的值,设计上是交替写入,这样设计避免了因介质失败致使没法找到可用的checkpoint的状况。
不一样的数据库有对应的重作日志格式。此外,InnoDB存储是基于页的,因此重作日志格式也是基于页的。虽然有不一样的重作日志格式,可是其有着通用的头部格式。
redo_log_type:重作日志的类型
space: 表空间的ID
page_no:页的偏移量
redo_log_body:根据重作日志类型不一样,会有不一样的存储内容,InnoDB1.2版本时,有51种重作日志格式,以后会愈来愈多。插入和删除的格式就不同。
这个是Log Sequence Number的缩写,表明的是日志的序列号。占8个字节,单调递增。含义是:
重作日志的写入字节总量
checkpoint的位置
页的版本
LSN不只记录在重作日志中,每一个页中的头部有一个值FIL_PAGE_LSN记录了该页的LSN。在页中表示页最后操做的LSN的大小。由于重作日志是每一个页的日志,因此能够经过LSN判断一个页是否须要进行恢复操做。如,P1页LSN是10000,数据库启动时检测到重作日志的LSN为13000,而且事务提交了,那么就须要进行恢复操做,将重作日志应用到P1页中。对于LSN小于P1页的LSN不须要进行重作,由于P1的LSN代表其已经操做完这些内容了。
数据库启动时会尝试进行恢复。由于重作日志记录的是物理日志,因此恢复速度比逻辑日志要快。InnoDB也对恢复进行了必定程度的优化,好比顺序读取并行应用重作日志,这样能够进一步提升数据库的恢复速度。
因为checkpoint表示已经刷新到磁盘页上的LSN,因此恢复只须要从checkpoint开始的日志部分。
重作日志记录了事务的行为,能够很好的经过其对页进行重作操做。可是事务有时候须要回滚,这时就须要undo。因此对数据库修改时,InnoDB存储引擎还会产生undo,回滚时就能够恢复原来的样子。
redo存放在重作日志文件中,undo放在数据库内部的一个特殊段中,这个就是undo段,位于共享表空间内。
undo并非将数据库物理地恢复到执行语句或事务以前的样子,其是逻辑日志,是经过逻辑恢复的方式。全部修改被逻辑的取消了,可是数据结构和页自己在回滚后可能不大相同。这是由于在多用户并发系统中,可能有不少个事务对数据记录并发访问,一个事务在修改一个页中的几条数据,另外一个事务在修改另外几个数据,不能将一个页回滚到事务开始的样子,以前提到过B+树修改可能会分裂,因此其中一个事务失败,回滚到以前的样子会影响到其余事务的执行。
undo的另外一个做用就是MVCC,若是用户读取一行记录,被其余事务占用了,能够经过undo读取以前的行版本信息,实现非锁定读取。
最后undo是逻辑日志,操做也会产生redo log,这是由于undo log也须要持久性的保护。
InnoDB存储引擎对undo采起段的方式管理,首先有rollback segment,每一个回滚段中记录了1024个undo log segment,而在每一个undo log segment段中进行undo页申请。共享表空间偏移量为5的页(0,5)记录了全部rollback segment header所在的页,这个页的类型为FIL_PAGE_TYPE_SYS。
1.1版本以前,只有一个rollback segment,因此支持的在线事务限制是1024,从1.1版本开始支持128个rollback segment,提升到了128*1024。这些数据依旧存储在共享表空间中。
1.2版本开始能够设置:
innodb_undo_directory: rollback segment文件所在的路径,意味着能够放在共享表之外的位置,默认"."表示当前InnoDB存储引擎的目录
innodb_undo_logs: 设置rollback segment的个数,默认128,替换了以前的innodb_rollback_segments参数
innodb_undo_tablespaces: 设置构成rollback segment文件的数量,这样rollback segment能够较平均地分布在多个文件中。能够看见undo前缀的文件,这个就是rollback segment文件了。
注意,undo log segment分配页并写入undo log这个过程也会写入重作日志,事务提交时,InnoDB会进行下面两件事:
将undo log放入列表中,供以后的purge操做
判断undo页是否能够重用,若能够分配给下个事务使用
事务提交后不会马上删除undo log,由于其余事务须要经过undo log获得行记录以前的版本,因此事务提交时将undo放入一个链表,是否能够删除undo log及其页由Purge线程判断。
此外,分配undo页会浪费空间,因此须要重用。具体来讲,事务提交时,将undo log放入链表,而后判断使用空间是否小于3/4,若小于表面能够被重用,以后的undo log记录在当前undo log的后面,因为存放undo log的记录的列表是以记录进行组织的,undo页可能存放不一样事务的undo log,因此purge操做须要涉及磁盘的离散读取操做,是一个比较慢的过程。
undo log分为:insert undo log和update undo log。
insert对于其余事务不可见,因此使用完后能够直接删除,不须要进行purge操做。
udpate对应delete和update操做,可能须要提升MVCC机制,因此不能直接删除,放入undo log链表中,等待purge肯定。
这两种日志结构比较复杂,不进行叙述。
information_schema下面有两张数据字典表:
INNODB_TRX_ROLLBACK_SEGMENT:查看rollback segment信息
INNODB_TRX_UNDO:记录事务对应的undo log。
delete操做并非直接删除记录,而只是将记录标记为已删除,记录的最终删除是在purge操做完成的。
update操做是分两步实现的,首先将原主键记录标记为删除,须要产生一条TRX_UNDO_DEL_MARK_REC的undo log,以后插入一条新的记录,产生一条TRX_UNDO_INSERT_REC的undo log。
上面提到delete和update操做并无直接删除数据,这些操做都是将页标记成删除状态,最终删除操做是purge操做中完成的。由于InnoDB支持MVCC,因此记录不能在事务提交时当即进行处理,这时其余事务可能正在引用这行。若是这条记录不被任何事务引用,就能够进行真正的删除操做。
前面说过为了节省空间,undo的页被设计成:一个页上容许有多个事务的undo log存在,虽然不表明事务在全局过程当中的提交顺序,可是后面的事务产生的undo log总在最后。此外,InnoDB还有一个history列表,它根据事务提交的顺序,将undo log进行连接。
在执行purge操做过程当中,会从history list中找到第一个须要被清理的记录,trx1,清理以后会找其关联的undo log所在的页中是否存在能够被清理的记录,进行清理。找完后会继续找下一个须要被清理的记录,继续清理undo页,查找该页上能够被清理的其余记录。
总的来讲就是先从history list找到undo log,再从undo page找到undo log,这样作的好处就在于避免大量随机读取操做,提升purge的效率。
全局动态参数innodb_purge_batch_size用来设置每次purge操做须要清理的undo page数量。InnoDB 1.2以前的默认值是20,以后默认是300。通常而言,设置的越大回收的undo page也就越多,这样可供重用的undo page就越多,减小了磁盘存储空间与分配的开销。不过,也不能设置的过大,会致使CPU和磁盘IO过于集中于对undo log的处理,性能降低。
另外一方面,若是压力很大时,不能有效的进行purge操做。那么history list的长度会愈来愈长。全局动态参数innodb_max_purge_lag用来控制history list的长度,若长度大于这个参数时,会延迟DML操做。默认值是0,不作限制,大于0时计算方法是:
(history_list.length - innodb_max_purge_lag) * 10 - 5 = delay,单位毫秒
延缓的对象是行,不是一个DML操做。好比一个update涉及5行,总延迟时间就是5*delay。
InnoDB1.2有一个新的动态参数innodb_max_purge_lag_delay,来控制这个delay的最大值,避免缓慢致使其余SQL线程出现无限制的等待。
事务非只读操做时,每次提交时会进行一次fsync操做,保证日志写入磁盘。为了提升fsync效率,就提供了group commit的功能,即一次fsync能够刷新确保多个事务日志被写入文件。
对于InnoDB而言,事务提交会进行两个阶段的操做:
修改内存中事务对应的信息,并将日志写入重作日志缓冲
调用fsync将确保日志都从重作日志缓冲写入磁盘。
第2个步骤比1步骤慢,当执行2步骤时,其余事务能够进行1步骤,再进行2步骤,能够将多个事务的重作日志经过一次fsync刷新到磁盘,这样就大大减少了磁盘的压力。
在InnoDB1.2以前,开始了二级制日志后,group commit功能会失效,而且在线环境多使用replication环境,所以二进制日志的选项基本为开启状态,这个问题尤其显著。致使这个问题的缘由在于:为了保证存储引擎层中的事务和二级制日志的一致性,两者之间使用了两段事务:
1.当事务提交时InnoDB进行prepare操做
2.MySQL数据库上层写入二进制日志
3.InnoDB存储引擎将日志写入重作日志文件
a.修改内存中事务对应的信息,而且将日志写入重作日志缓冲。
b.调用fsync将确保日志都从重作日志缓冲写入磁盘。
一旦步骤2完成,就确保了事务的提交,即便在执行步骤3时数据库发生了宕机。每一个步骤须要执行一次fsync操做才能保证上下两层数据的一致性。步骤2的fsync由参数sync_binlog控制,步骤3的fsync由参数innodb_flush_log_at_trx_commit控制。
问题就出在顺序上面,为了保证二级制写入顺序和InnoDB的事务提交顺序,会使用prepare_commit_mutex锁,第三步的写入重作日志缓冲操做就不能在其余事务执行fsync操做时进行了,破坏了group commit。二级制顺序必须保证,由于备份和恢复时须要顺序的。
这个问题在2010年MySQL数据库打会中提出,后面有了解决方案,MySQL 5.6采起了相似的方式实现,称为Binary Log Group Commit(BLGC)。
数据库上层进行提交时,先按顺序放入队列中,队列的第一个事务为leader,其余的是follower。BLGC的步骤以下:
1.flush阶段,将每一个事务的二级制日志写入内存中
2.Sync阶段,将内存的二进制日志刷新到磁盘,多个就只须要一次fsync就能完成,这就是BLGC
3.commit阶段,leader按照顺序调用存储引擎层事务的提交,InnoDB本就支持group commit,就没问题了。
当一组事务进行Commit时,其余的能够进行flush,使得group commit生效,group commit的效果和队列中事务的数量相关,数量越多,效果越好。
参数binlog_max_flush_queue_time用来控制flush阶段中等待的时间,即便以前的一组事务完成提交,当前的也不会立刻进入sync,等待一段时间。好处就是group commit的事务就更多了,这可能会致使事务响应时间变慢。默认为0,推荐使用这个值。除非有100个链接,而且在不断的写入或更新操做。
MySQL命令行下事务是默认提交的。须要显示的开启一个事务BEGIN、START TRANSACTION或者执行命令SET AUTOCOMMIT=0。
事务控制语句:
START TRANSACTION | BEGIN:显示地开启一个事务
COMMIT: 提交事务,修改为为永久的,COMMIT WORK含义相似
ROLLBACK:回滚会结束用户的事务,并撤销正在进行的全部未提交的修改, ROLLBACK WORK含义相似
SAVEPOINT indentifier:SAVEPOINT容许在事务中建立一个保存点,能够有多个保存点
RELEASE SAVEPOINT identifier:删除一个事务的保存点,没有保存点时会抛出异常
ROLLBACK TO [SAVEPOINT] identifier:这个语句和SAVEPOINT一块儿使用。能够把事务回滚到标记点,以前的工做保留。
SET TRANSACTION: 设置事务的隔离级别。InnoDB中有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。
COMMIT WORK的不一样之处在于用来控制事务结束后的行为是CHAIN仍是RELEASE,若是是CHAIN就变成了链事务。能够经过参数completion_type进行控制,默认是0,表示没有任何操做,和COMMIT相同。为1时就等同于COMMIT AND CHAIN,表示立刻自动开启一个相同隔离级别的事务。为2时等同于COMMIT AND RELEASE,在事务提交后会自动断开与服务器的链接。
InnoDB存储引擎中的事务都是原子的,构成事务的语句都会提交或者回滚。延伸单个语句就是要么所有成功,要么彻底回滚,所以一条语句失败并抛出异常时,不会致使先前执行的语句自动回滚,由用户本身决定是否对其进行提交或回滚操做。
另外一个容易犯错的就是ROLLBACK TO SAVEPOINT,虽然有回滚,可是事务并无结束,须要显示执行COMMIT或者ROLLBACK。
如下的SQL语句会产生一个隐式的提交操做,即执行完后会有隐式的COMMIT操做。
DDL语句:ALTER DATABASE... UPGRADE DATA DIRECTORY NAME,
ALTER EVENT、 ALTER PROCEDURE、ALTER TABLE、 ALTER VIEW、
CREATE DATABASE、 CREATE EVENT、 CREATE INDEX、 CREATE PROCEDURE、
CREATE TABLE、 CREATE TRIGGER、 CREATE VIEW、 DROP DATABASE、 DROP EVENT、
DROP INDEX、DROP PROCEDURE、 DROP TABLE、 DROP TRIGGER、 DROP VIEW、RENAME TABLE、
TRUNCATE TABLE
修改MySQL架构操做: CREATE USER、 DROP USER、 GRANT、 RENAME USER、 REVOKE、 SET PASSWORD
管理语句: ANALYZE TABLE、CACHE INDEX、 CHECK TABLE、 LOAD INDEX、 INTO CACHE、 OPTIMIZE TABLE、 REPAIR TABLE
因为InnoDB是支持事务的,所以须要考虑每秒请求数QPS,同时要关注每秒事务的处理能力TPS。
计算TPS的方法是(com_commit+com_rollback)/time。前提是事务必须是显示提交的,存在隐式事务的提交和回滚(默认autocommit=1),不会计算到com_commit和com_rollback中。
还有两个参数handler_commit和handler_rollback用于事务的统计操做。
大部分数据库系统都没有提供真正的隔离性,最初多是由于对这些问题的理解不到位。如今虽然清楚了,可是在正确性和性能之间作了妥协。ISO和ANIS SQL标准制定了4种事务隔离级别,可是不多有遵照这些标准的。标准的隔离级别以下:
1.READ UNCOMMITTED:称为浏览访问,仅仅针对事务而言的。
2.READ COMMITTED:称为游标稳定
3.REPEATABLE READ:2.9999°,没有幻读的保护
4.SERIALIZABLE:3°隔离,这个是默认的隔离级别
INNODB默认的隔离级别是REPEATABLE READ,可是和标准不一样的是,其经过Next-key lock锁的算法,避免了幻读的产生。
隔离级别越低,事务请求的锁越少或保持锁的时间越短。这也就是为何大部分数据库的事务隔离级别是READ COMMITTED了。
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL
{
READ UNCOMMITTED
| READ COMMITTED
| REPEATABLE READ
| SERIALIZABLE
}
也能够经过配置文件,在MySQL启动时就设置默认的隔离级别:
[mysqld]
transaction-isolation=READ-COMMITTED
查看当前会话的事务隔离级别 select @@tx_isolation
在SERIALIABLE的事务隔离级别,InnoDB会对每一个SELECT语句后自动加上LOCK IN SHARE MODE,这样对一致性非锁定度再也不支持,理论上是符合数据库要求的,即事务是well-formed的,而且是two-phrased的。
由于InnoDB默认的REPEATABLE READ就能达到3°的隔离,因此通常事务不须要使用SERIALIABLE的隔离级别,那个主要用于分布式事务。
对于READ COMMITTED事务隔离级别下,除了惟一性的约束检查及外键约束的检测须要gap lock,InnoDB不会使用gap lock锁算法。可是,在MySQL5.1中,READ COMMITTED事务隔离级别默认只能工做在replication二进制日志为ROW的格式下,若是是STATEMENT,会出错误。在MySQL5.0版本之前,不支持ROW格式的二进制时,经过innodb_locks_unsafe_for_binlog设置为1能够在二进制日志为STATEMENT下使用READ COMMITTED的事务隔离级别,可是可能会形成主从不一致的现象。
在READ COMMITTED隔离级别下,没有使用gap lock锁定,致使能够插入数据。
在STTATEMENT格式记录的是master上产生的SQL语句,因此在master上是先删后插入,可是STATMENT格式的记录倒是先插后删,逻辑顺序致使了不一致。
能够经过REPEATABLE隔离级别避免这个问题,MySQL5.1版本以后支持了ROW格式,就避免了这个问题。
InnoDB存储引擎提供了对XA事务的支持,并经过XA事务来支持分布式事务的实现。分布式事务指的是容许多个独立的事务资源参与到一个全局的事务中。在使用分布式事务的时候,InnoDB必须设置成SERIALIZABLE。
XA事务容许不一样数据库之间的分布式事务,好比一台服务是MySQL,一台是Oracle等。只要参与在全局事务中的每一个节点都支持XA事务。
XA事务由一个或多个资源管理器、一个事务管理器以及一个应用程序组成。
资源管理器:提供访问事务资源的方法。一般一个数据库就是一个资源管理器
事务管理器:协调参与全局事务中的各个事务,须要和参与全局事务的全部资源管理器进行通讯
应用程序:定义事务的边界,指定全局事务中的操做。
在MySQL中,资源管理器就是MySQL数据库,事务管理器为链接MySQL服务器的客户端。
分布式事务使用两段式提交的方式:
第一阶段,全部参与全局事务的节点都开始准备,告诉事务管理器它们准备好提交了
第二阶段,事务管理器告诉资源管理器执行ROLLBACK仍是COMMIT。若是任何一个节点不能提交,全部节点被告知回滚。
XA {start | begin} xid {Join | resume}
XA END xid [SUSPEND [for migrate]]
XA PREPARE xid
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
XA RECOVER
当前JAVA的JTA能够很好的支持MySQL的分布式事务,须要使用分布式事务应该认真参考其API。
以前讨论的分布式事务是外部事务,即资源管理器是MySQL数据库自己。还有一种分布式事务,其在存储引擎与插件之间,又或者是存储引擎和存储引擎之间,称为内部XA事务。
最多见的就是binlog与InnoDB之间的了,因为复制的须要,大部分数据库开启了binlog功能。提交时,先写二进制日志,再写重作日志,这两个操做必须是原子性的。因此使用了XA事务,在提交时,先作一个PREPARE操做,将事务的xid写入,接着进行二级制日志的写入。若是在InnoDB存储引擎提交前,MySQL宕机了,在重启后会检查准备的uxid事务是否提交,没有就会在存储引擎层再进行一次提交操做。
顾名思义,就是执行时间较长的事务,好比银行系统,每一个阶段就要更新帐户利息。对应的帐号数量很是大。
update account set account_total = account_total + (1 + interest_rate)
问题在于若是故障了就要从新开始,这个代价太大了,回滚操做就有可能耗时巨大。所以对于长事务的处理,能够转化为小批量的事务来进行处理。当事务发送错误时,只须要回滚一部分数据,而后接着上次已完成的事务继续执行。