Mysql基础——事务

内容

  • 事务
    • 事务基本知识
    • 事务特性如何实现的?
    • 快照读和当前读
  • 分布式事务
    • 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:它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果;
    1. 一致性读视图:InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。
  • 事务的启点?
    1.事务的启动是在执行第一个语句的时候才开始的,不是从begin开始;一致性读视图是执行第一个快照读的时候建立的;
    2.如果用start transaction with consistent snapshot代替begin启动一个事务,那么一致性读视图是在执行这个命令的时候建立的
  • mysql如何实现“秒”级快照,即使一个库里有几百G数据?
    1. 在RR隔离级别时,在一个事务内,当执行第一个快照读时会建立一个一致性快照读,之后本事务内所有的查询都用这个视图,以保证事务之间的隔离性。
    2. RR隔离级别时,事务启动时会向事务管理器申请一个事务id,这个事务ID全局递增,每行数据的每个版本都会用这个事务id标识,是事务启动时声明:“以我启动的时刻为准,如果一个数据版本是在我启动之前生成的,就认;如果是我启动以后才生成的,我就不认,我必须要找到它的上一个版本”。
    3. 每行数据会有多个版本,各个版本是通过undo日志计算出来的,在数据更新时会记录一个逆向操作的undo日志,通过undo日志就可以推算出各个版本的数据,每个版本是通过事务id来标识的
  • 当前读:更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。除了 update 语句外,select 语句如果加锁,也是当前读,例如:查询语句 select * from t where id=1 加上 lock in share mode 或 for update,就是当前读;

二 分布式事务

2.1 XA协议

分布式事务处理的XA规范如下所示:

Mysql基础——事务_第1张图片
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事务使用的基本语法如下:


Mysql基础——事务_第2张图片
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   
  1. 当xa start开启事务后,DML也会在对应的RM上创建undo以及read view。
  2. 当xa prepare 时会将子事务置于PREPARED状态,此时子事务已经完成事务提交前的所有准备工作(获得锁,并将PREPARED状态记录到共享表空间中,会将xa start到xa end之间操作记录在binlog中)。
  3. 当xa commit 时会在binlog中记录xa commit xid, 并将innodb中PREPARED状态转化为COMMITED状态。
  4. 当xa commit one phase 时会同时进行prepare和commit 两种操作,是在TM发现全局的分布式事务只涉及一个RM时进行的(因为不需要等待其他RM的反馈结果)。
  5. 当xa rollback在xa prepare前时,因为没有写binlog和redo,只会释放undo, read view以及lock。
  6. 当xa rollback 在xa prepare之后时,除了需要释放undo, read view以及lock,还需要binlog中记录xa rollback xid(使得从库不会提交该事务)以及innodb中将PREPARED状态转化为ROLLBACK状态。

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时数据也不会丢失的。

引用:

  • 《丁奇45讲》
  • MySQL 中基于 XA 实现的分布式事务
  • MySQL XA 介绍

你可能感兴趣的:(Mysql基础——事务)