事务隔离性由锁来实现。原子性、一致性、持久性经过数据库的redo log和undo log来完成。redo log称为重作日志,用来保证事务的原子性和持久性。undo log用来保证事务的一致性。java
redo和undo做用都是一种恢复操做。mysql
基本概念算法
重作日志用来实现事务的持久性,由两部分组成sql
redo log buffer
redo log file
InnoDB是事务的存储引擎,其经过Force Log at Commit
机制来实现事务的持久性,即当事务提交时,必须将该事务的全部日志写入到重作日志文件进行持久化,该事务的COMMIT操做才算完成。这里的重作日志文件包括redo 和 undo log。
用户也能够手工设置COMMIT日志刷新策略,经过参数innodb_flush_log_at_trx_commit
来控制。数据库
log block缓存
在INNODB中,重作日志都是以512字节进行存储的,由于大小和磁盘扇区大小同样,所以重作日志的写入能够保证原子性。服务器
log group并发
称为重作日志组,其中有多个重作日志文件,可是INNODB进行这个功能,实际上也只有一个log group。分布式
是一个逻辑上的概念,由多个重作日志文件组成,每一个log group中的日志文件大小是相同的。大小最大为512GB。ide
log buffer也是使用块进行存储的管理,一样为512字节。从缓存刷新到磁盘的具体规则为:
重作日志格式
INNODB的重作日志格式是基于页的。虽然有这边冉的重作日志格式,可是他们有着通用的头部格式。
redo_log_type | space | page_no | redo log body |
---|---|---|---|
LSN
Log Sequence Number
缩写,表示日志序列号。在INNODB存储引擎中,LSN占用8字节,而且单调递增,LSN表示含义有:
重作日志写入的总量
若当前重作日志的LSN为1000,一个事务写入了100字节的重作日志,那么LSN变为1100。
checkpoint的位置
页的版本
每一个页的头部,有一个值FILE_PAGE_LSN
,记录该页的LSN。表示该页最后刷新时LSN的大小。用于判断页是否须要进行恢复操做。例如P1的LSN为10000,数据启动时,检测到重作日志文件中的LSN为13000,而且该事务已经提交,那么数据库就要对P1进行恢复操做。
恢复
无论运行时是否正常关闭,都会尝尽进行恢复操做。由于记录的都是物理日志,因此恢复速度比逻辑日志快不少。
基本概念
进行回滚操做时使用。undo存放在数据库内部的一个特殊段中,成为undo段。位于共享表空间中。undo是逻辑日志,是将数据库逻辑地恢复到原来的样子。这是由于在多用户并发系统中,可能有成百上千个并发事务,若是直接物理回滚页记录,会影响其余正在进行的事务。
因此undo的回滚操做是逻辑操做,对于insert,进行对应的delete;对于delete,执行对象的Insert,对于update,进行反向的update。
undo的另一个做用是MVCC,实现了非锁定读取。
undo log也会产生redo log。这是由于undo log也须要持久性的保护。
undo存储管理
采用段的方式进行管理,首先有rollback segment
,每一个回滚段中记录了1024个undo log segment
,而在每一个undo log segment
段中进行undo页的申请。
对rollback segment
作进一步的设置:
innodb_undo_directory
用于设置文件所在的路径。便可以设置为独立表空间。该参数默认值为“.”,表示当前INNODB存储引擎的目录。
innodb_undo_logs
用来设置rollback segment
个数,默认值为128。
innodb_undo_tablespaces
设置构成rollback segment
文件的数量,这样rollback segment
能够较为平均地分布在多个文件中。
SHOW VARIABLES LIKE 'innodb_undo%'; SHOW VARIABLES LIKE 'datadir';
事务在undo log segment
分配页并写入undo log
的这个过程一样须要写入重作日志。
undo log
放入列表中,以供以后的purge
操做undo log
所在的页是否能够重用,若能够分配给下个事务使用。事务提交以后并不能立刻删除undo log以及undo log所在的页,这是由于可能有MVVC使用。因此将undo log放在一个链表中,是否能够最终删除由purge线程判断。
undo log
格式
有两种:
insert undo log
在insert操做中产生的undo log。因为事务隔离性的要求,因此该undo log能够在事务提交后直接删除。不须要进行Purge操做。
update undo log
对delete和update操做产生的undo log
。改undo log
可能须要提供MVCC机制,所以不能在事务提交时就进行删除。
查看undo 信息
# 查看rollback segment DESC INNODB_TRX_ROLLBACK_SEGMENT; # 查看rollback segment所在的页 SELECT segment_id, space, page_no from INNODB_TRX_ROLLBACK_SEGMENT; # 记录事务对应的undo log SELECT * FROM information_schema.INNODB_TRX_UNDO\G;
purge
delete和update操做可能并不直接删除原有的数据,undo log只将对应记录的delete flag设置为1,没有直接删除记录,真正删除的操做被延时到purge操做中完成。
由于MVVC的关系,purge要等待该行记录已经不被任何其余事务引用,才进行清理操做。
INNODB存储引擎中含有一个history list
,按照事务提交的顺序将undo log
进行组织。在执行purge
的过程当中,INNODB存储引擎首先从history list
中找到第一个须要被清理的记录,清理以后,会继续在该记录所在的undo log页中继续寻找能够被清除的记录,而后再继续从history list
中去查找,重复执行。
参数innodb_purge_batch_size
用来设置每次Purge操做须要清理的undo page
数量。
参数innodb_max_purge_lag
用来控制history list
的长度,若长度大于该参数时,其会“延缓”DML的操做。默认为0,表示不作任何限制。当大于0时,就会延缓DML的操做,延缓的算法为:
delay = ((length(history_list) - innodb_max_purge_lag) * 10) - 5
单位是毫秒。delay的对象是行,而不是DML操做。
参数innodb_max_purge_lag_delay
,用来控制delay的最大毫秒数。当上述计算的delay数值大于该参数值,限制住。
group commit
若事务为非只读事务,则每次提交都要进行依次fsync
操做,以此保证重作日志都已经写入磁盘。为了提升磁盘fsync效率,提供了group commit
功能,依次fsync能够刷新确保多个事务日志被写入文件。
可是在开启了二进制日志以后,为保证存储引擎层中的事务和二进制日志的一致性,两者之间使用了两阶段事务,步骤以下:
一旦写入了二进制日志,就确保了事务的提交,即便执行步骤3发生了宕机。此外,每一个步骤都须要进行依次fsync才能保证上下两层数据一致。步骤二由sync_binlog
控制,步骤三由innodb_flush_log_at_trx_commit
控制。
由于备份以及恢复的须要,须要保证MYSQL数据库上层二进制日志的写入顺序和INNODB层的事务提交顺序一致,MYSQL数据库内部使用了prepare_commit_mutex
这个锁。可是启用这个锁以后,步骤3中的步骤1不能够再其余事务执行步骤二时执行。从而致使group commit
失效。
为了解决这个问题,5.6版本以后使用了BLGC
技术。
在MYSQL数据库上层进行提交时先按照顺序将其放入一个队列中,队列中的第一个事务成为leader,其余事务成为follower,leader控制follower的行为。BLGC的步骤分为如下三个阶段:
Flush
阶段:将每一个事务的二进制日志写入内存。Sync
阶段:将内存中的二进制日志刷新到磁盘,若队列中有多个事务,那么仅一次fsync操做就完成了二进制日志的写入,这就是BLGC
。Commit
阶段,leader根据顺序调用存储引擎层事务的提交,InnoDB存储引擎本就支持Group commit
,因此就解决了gourp commit
失效的问题。 当一组事务在进行commit
阶段时,其余新事务能够进行Flush
阶段,从而使group commit
不断生效。group commit
的效果由队列中书屋的数量决定,若每次队列中仅有一个事务,那么可能效果和以前差很少,甚至会更差。但当提交的事务越多时,group commit的效果越明显,数据库性能的提高也就越大。
参数binlog_max_flush_queue_time
用来控制Flush
阶段中等待的时间,即便以前的一组事务完成提交,当前一组的事务也不立刻进入Sync
阶段,而是至少须要等待一段时间。这个好处是group commit
数量更多,该参数默认值为0,推荐这是依旧为0。
START TRANSACTION | BEGIN
:显式地开启一个事务COMMIT
:提交事务ROLLBACK
:回滚会结束用户的事务,并撤销正在进行的全部未提交的修改SAVEPOINT identifier
:容许在事务中建立一个保存点,一个事务中能够有多个SAVEPOINT
。RELEASE SAVEPOINT identifier
:删除一个事务的保存点,当没有一个保存点执行这句语句时,会抛出一个异常。ROLLBACK TO [SAVEPOINT] identifier
:与SAVEPOINT
命令一块儿使用,能够把事务回滚到标记点,而不会管在此标记点以前的任何工做。SET TRANSACTION
:用来设置事务的隔离级别
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
COMMIT WORK
用来控制事务结束后的行为是CHAIN
仍是RELEASE
。若是是CHAIN
方式,那么事务就变成了链事务。
经过参数completion_type
来控制
COMMIT
是彻底等价的COMMIT AND CHAIN
,表示立刻自动开启一个相同隔离级别的事务。COMMIT AND RELEASE
,表示事务提交以后会自动断开与服务器的链接。QPS Question Per Second
:每秒请求数
TPS Transaction Per Second
:每秒事务处理能力
计算TPS的方法是(com_commit + com_rollback) / time。计算前提是全部事务必须是显式提交的。
SQL标准定义的四个隔离级别为:
READ UNCOMMITTED
:浏览访问READ COMMITED
:游标稳定REPEATABLE READ
:是2.9999°的隔离,没有幻读的保护。SERIALIZABLE
:称为隔离,或者3°的隔离。 MYSQL引擎默认支持的隔离级别是REPEATABLE READ
,可是已经经过Next-Key Lock
锁的算法,避免了幻读产生。达到SQL标准的SERIALIZABLE
隔离界别。
隔离级别越低,事务请求的锁越少或者保持锁的时间就越短。默认的事务隔离级别是READ COMMITTED
。
修改MYSQL启动时设置的默认隔离级别,须要修改MYSQL的配置文件
[mysqld] transaction-isolation = READ-COMMITTED
如何查看事务隔离级别
# 当前会话的事务隔离级别 SELECT @@tx_isolation\G; # 全局的事务隔离级别 SELECT @@global.tx_isolation\G;
主从复制若是发生了不一致,可能发生的缘由有两点:
READ COMMITTED
事务隔离级别下,事务没有使用gap lock
进行锁定,所以用户在会话B中能够在小于等于5的范围内插入一条记录。STATEMENT
格式记录的是master
上产生的SQL语句,所以在master服务器上执行的顺序为先删后插,可是STATEMENT
格式记录的确实先插后删。逻辑顺序上产生了不一致。 INNODB存储引擎提供了对XA事务的支持,并经过XA事务来支持分布式事务的实现。分布式事务指的是容许多个独立的事务资源参与到一个全局的事务中。全局事务要求在其中的全部参与的事务要么都提交,要么都回滚。
INNODB存储引擎的事务隔离级别必须设置为SERIALIZABLE
。
XA事务容许不一样数据库以前的分布式事务。分布式事务可能在银行的转帐系统中比较常见。
XA事务由一个或多个资源管理器、一个事务管理器以及一个应用程序组成。
分布式事务采用二段式提交的方式。
ROLLBACK
仍是COMMIT
。若是任何一个节点显示不能提交,则全部的节点都被告知须要回滚。 MYSQL数据库XA事务的SQL语法以下:
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
可是通常来讲,单个节点上运行分布式事务没有实际意义,通常和变成语言来完成分布式事务的操做。
@Data @Builder @NoArgsConstructor @AllArgsConstructor class MyXid implements Xid { public int formatId; public byte gtrid[]; public byte bqual[]; } public class xa_demo { public static MysqlXADataSource GetDataSource(String conString, String user, String passwd) { try { MysqlXADataSource ds = new MysqlXADataSource(); ds.setUrl(connString); ds.setUser(user); ds.setPassword(passwd); return ds; } catch(Exception e) { System.out.println(e.toString()); return null; } } } public static void main() { String connString1 = "jdbc:mysql://192.168.24.43:3306/bank_shanghai"; String connString2 = "jdbc:mysql://192.168.24.44:3306/bank_beijing"; try { MysqlXDataSource ds1 = GetDataSource(connString1, "mxr", "12345"); MysqlXDataSource ds2 = GetDataSource(connString2, "cxw", "12345"); XAConnection xaConn1 = ds1.getXAConnect(); XAResource xaRes1 = xaConn1.getXAResource(); Connection conn1 = xaConn1.getConnection(); Statement stmt1 = conn1.createStatement(); XAConnection xaConn2 = ds2.getXAConnect(); XAResource xaRes2 = xaConn2.getXAResource(); Connection conn2 = xaConn2.getConnection(); Statement stmt2 = conn2.createStatement(); Xid xid1 = new MyXid(100, new byte[]{0x01}, new byte[]{0x02}); Xid xid2 = new MyXid(100, new byte[]{0x11}, new byte[]{0x12}); try { xaRes1.start(xid1, XAResource.TMNOFLAGS); stmt1.execute("UPDATE account SET money = money - 10000 where user = 'mxr'"); xaRes1.end(xid1, XAResource.TMSUCCESS); xaRes2.start(xid2, XAResource.TMNOFLAGS); stmt2.execute("UPDATE account SET money = money + 10000 where user = 'cxw'"); xaRes2.end(xid2, XAResource.TMSUCCESS); int ret2 = xaRes2.prepare(xid2); int ret1 = xaRes2.prepare(xid1); if( ret1 = XAResource.XA_OK && ret2 = XAResource.XA_OK) { xaRes1.commit(xid1, false); xaRes2.commit(xid2, false); } } catch(Exception e) { e.printStackTrace(); } } catch(Exception e) { System.out.println(e.toString()); } }
经过参数innodb_support_xa
能够查看是否启用了XA事务的支持(默认为ON);
在MYSQL内,存储引擎与插件之间,存储引擎之间也存在一种分布式事务,成为内部XA事务。
最多见的是binlog
与INNODB
存储引擎之间内部XA事务。binlog和存储引擎的重作日志必须同时写入,保证原子性,不然会致使主备数据不一致。
CREATE PROCEDURE load(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a', 80); WHILE S <= count DO INSERT INTO t1 SELECT NULL, c; SET S = S + 1; END WHILE; END;
每次的insert都会发生自动提交,若是用户插入10000条数据,在5000条是发生了错误,那这5000已存在的数据如何处理?
若是每次都提交,每次都须要写重作日志,影响效率。
因此建议使用同一个事务
CREATE PROCEDURE load(count INT UNSIGNED) BEGIN DECLARE s INT UNSIGNED DEFAULT 1; DECLARE c CHAR(80) DEFAULT REPEAT('a', 80); START TRANSACTION; WHILE S <= count DO INSERT INTO t1 SELECT NULL, c; SET S = S + 1; END WHILE; COMMIT; END;
编写应用程序开发时,最好把事务的控制权限交给开发人员,在程序端进行事务的开始和结束。
使用自动回滚以后,MYSQL在程序段是不会抛出异常信息的,不便于调试,因此通常存储过程当中值存放逻辑操做便可。管理操做所有放在java中进行。
长事务:执行时间较长的事务。
这边通常会将长事务拆解为多个小事务进行操做。这样子,若是发生是失败了,能够继续在失败的小事务上继续进行重试,而不用所有重试。节省时间。