内容
事务
事务基本知识
事务特性如何实现的?
快照读和当前读
分布式事务
XA规范
mysql基于XA实现的分布式事务
mysql如何保证数据不丢失?
一 事务
1.1 事务基本知识
事务的特性:原子性、一致性、隔离性、持久性
并发事务可能的问题:脏读、不可重复读、幻读
事务隔离级别:读未提交、读提交、可重复读、串行化
读未提交:一个事务还未提交,它所做的变更就可以被别的事务看到;
读提交:一个事务提交之后,它所做的变更才可以被别的事务看到;
可重复读:一个事务执行过程中看到的数据是一致的。未提交的更改对其他事务是不可见的;
串行化:对应一个记录会加读写锁,出现冲突的时候,后访问的事务必须等前一个事务执行完成才能继
事务隔离性实现:每条记录在更新的时候都会同时记录一条回滚操作。同一条记录在系统中可以存在多个版本,这就是数据库的多版本并发控制(MVCC)。
回滚日志什么时候删除?系统会判断当没有事务需要用到这些回滚日志的时候,回滚日志会被删除。
什么时候不需要了?当系统里么有比这个回滚日志更早的read-view的时候。
为什么尽量不要使用长事务。长事务意味着系统里面会存在很老的事务视图,在这个事务提交之前,回滚记录都要保留,这会导致大量占用存储空间。除此之外,长事务还占用锁资源,可能会拖垮库。
事务启动方式:一、显式启动事务语句,begin或者start transaction,提交commit,回滚rollback;二、set autocommit=0,该命令会把这个线程的自动提交关掉。这样只要执行一个select语句,事务就启动,并不会自动提交,直到主动执行commit或rollback或断开连接。
事务使用建议:如果考虑多一次交互问题,可以使用commit work and chain语法。在autocommit=1的情况下用begin显式启动事务,如果执行commit则提交事务。如果执行commit work and chain则提交事务并自动启动下一个事务。
1.2 事务特性如何实现的?
原子性、一致性和持久性
原子性、一致性和持久性是通过redo log和undo log来实现的:
原子性: 实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
持久性:实现持久性是通过redo log完成的,当数据修改时,会把这个操作写入redo log,当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。
隔离性
隔离性是通过锁机制和mvcc机制实现的,通过mvcc实现一个事务的快照读不受另外一个事务当前读的操作影响,通过锁机制控制两个事务对同一行数据的隔离更新操作;
1.3 快照读与当前读
mysql中的两个“视图”:
1.view:它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果;
一致性读视图:InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。
事务的启点?
1.事务的启动是在执行第一个语句的时候才开始的,不是从begin开始;一致性读视图是执行第一个快照读的时候建立的;
2.如果用start transaction with consistent snapshot代替begin启动一个事务,那么一致性读视图是在执行这个命令的时候建立的
mysql如何实现“秒”级快照,即使一个库里有几百G数据?
在RR隔离级别时,在一个事务内,当执行第一个快照读时会建立一个一致性快照读,之后本事务内所有的查询都用这个视图,以保证事务之间的隔离性。
RR隔离级别时,事务启动时会向事务管理器申请一个事务id,这个事务ID全局递增,每行数据的每个版本都会用这个事务id标识,是事务启动时声明:“以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本”。
每行数据会有多个版本,各个版本是通过undo日志计算出来的,在数据更新时会记录一个逆向操作的undo日志,通过undo日志就可以推算出各个版本的数据,每个版本是通过事务id来标识的
当前读:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。除了 update 语句外,select 语句如果加锁,也是当前读,例如:查询语句 select * from t where id=1 加上 lock in share mode 或 for update,就是当前读;
二 分布式事务
2.1 XA协议
分布式事务处理的XA规范如下所示:
image.png
可知XA规范中分布式事务有AP,RM,TM组成:
其中应用程序(Application Program ,简称AP):AP定义事务边界(定义事务开始和结束)并访问事务边界内的资源。
资源管理器(Resource Manager,简称RM):Rm管理计算机共享的资源,许多软件都可以去访问这些资源,资源包含比如数据库、文件系统、打印机服务器等。
事务管理器(Transaction Manager ,简称TM):负责管理全局事务,分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚、失败恢复等。
XA协议是使用了二阶段协议的,其中:
第一阶段TM要求所有的RM准备提交对应的事务分支,询问RM是否有能力保证成功的提交事务分支,RM根据自己的情况,如果判断自己进行的工作可以被提交,那就就对工作内容进行持久化,并给TM回执OK;否者给TM的回执NO。RM在发送了否定答复并回滚了已经的工作后,就可以丢弃这个事务分支信息了。
第二阶段TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare回执NO的话,则TM通知所有RM回滚自己的事务分支。
2.2 mysql基于XA实现的分布式事务
在MySQL数据库分布式事务中,MySQL是XA事务过程中的资源管理器(RM)存在的,TM是连接MySQL服务器的客户端。MySQL数据库是作为RM存在的,在分布式事务中一般会涉及到至少两个RM,所以我们说的MySQL支持XA协议是说mysql作为RM来说的,也就是说MySQL实现了XA协议中RM应该具有的功能;
MySQL中只有InnoDB引擎支持XA协议
mysql中xa事务使用的基本语法如下:
image.png
看一个基本的使用例子:
mysql> xa start '111';
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t values(1, "a");
Query OK, 1 row affected (0.00 sec)
mysql> insert into t values(2, "b");
Query OK, 1 row affected (0.00 sec)
mysql> xa end '111';
Query OK, 0 rows affected (0.00 sec)
mysql> xa prepare '111';
Query OK, 0 rows affected (0.00 sec)
mysql> xa commit '111';
Query OK, 0 rows affected (0.01 sec)
看一下binlog[row格式],start、end和commit都会产生一个query event, prepare会产生一个XA PREPARE event
log.000016 | 887 | Query | 1 | 981 | XA START X'313131',X'',1 |
| binlog.000016 | 981 | Table_map | 1 | 1036 | table_id: 345 (test.t) |
| binlog.000016 | 1036 | Write_rows | 1 | 1079 | table_id: 345 flags: STMT_END_F |
| binlog.000016 | 1079 | Table_map | 1 | 1134 | table_id: 345 (test.t) |
| binlog.000016 | 1134 | Write_rows | 1 | 1177 | table_id: 345 flags: STMT_END_F |
| binlog.000016 | 1177 | Query | 1 | 1269 | XA END X'313131',X'',1 |
| binlog.000016 | 1269 | XA_prepare | 1 | 1308 | XA PREPARE X'313131',X'',1 |
| binlog.000016 | 1308 | Gtid | 1 | 1385 | SET @@SESSION.GTID_NEXT= '38c9a80a-ac85-11ea-8fbf-0242c0a80a02:28' |
| binlog.000016 | 1385 | Query | 1 | 1480 | XA COMMIT X'313131',X'',1
其中首先使用XA START ‘xid' 启动了一个XA事务,并把它置于ACTIVE状态
对于一个ACTIVE状态的 XA事务,我们可以执行构成事务的多条SQL语句,然后通过XA END 指定这个事务执行sql结束的边界;
对于一个IDLE 状态XA事务,可以执行一个XA PREPARE语句或一个XA COMMIT…ONE PHASE语句,其中XA PREPARE把事务放入PREPARED状态。在此点上的XA RECOVER语句将在其输出中包括事务的xid值,因为XA RECOVER会列出处于PREPARED状态的所有XA事务。XA COMMIT…ONE PHASE用于预备和提交事务,也就是转换为一阶段协议,直接提交事务。
对于一个PREPARED状态的 XA事务,可以执行XA COMMIT 语句来提交或者执行XA ROLLBACK来回滚xa事务。
2.3 mysql如何保证数据不丢失?
总的来说,MySQL是通过binlog和redo log两者来保证数据不丢失的,mysql 采用WAL的机制,内部也通过两阶段提交(内部XA)来保证binlog和redo log的写入一直性,看一下两阶段提交流程图:
image.png
在回顾一下更新数据的基本流程:
当一个事务更新数据时,会把相应的redo log写入到redo log buffer中,redo log buffer是一快内存,然后出于prepare阶段,如果 innodb_flush_log_at_trx_commit 设置成 1,那么 redo log 在 prepare 阶段就要持久化一次到磁盘;
然后写入binlog;
最后把事务状态置于commit状态
问题:如果在时刻B时,即已经把写入binlog了,但是在更改状态为commit时,数据库crash了,崩溃恢复时如何处理?
mysql是这样处理的:
如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;
如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
a. 如果是,则提交事务;
b. 否则,回滚事务。
问题二:MySQL 怎么知道 binlog 是完整的?
一个事务的 binlog 是有完整格式的:statement 格式的 binlog,最后会有 COMMIT;row 格式的 binlog,最后会有一个 XID event。另外,在 MySQL 5.6.2 版本以后,还引入了 binlog-checksum 参数,用来验证 binlog 内容的正确性。对于 binlog 日志由于磁盘原因,可能会在日志中间出错的情况,MySQL 可以通过校验 checksum 的结果来发现。所以,MySQL 还是有办法验证事务 binlog 的完整性的。
总结:mysql 通过两阶段提交,内部binlog和redo log处理板块也通过XA事务的方式来两保证两者的处理一致性来保证数据库发生异常crask时数据也不会丢失的。
引用: