事务
一个不可分割的数据库操作序列,是数据库并发控制的基本单位。
事务是逻辑上的一组操作,要么都执行,要么都不执行。
ACID
- atomicity(原子性) :要么全执行,要么全都不执行;
- consistency(一致性):在事务开始和完成时,数据都必须保持一致状态;
- isolation(隔离性) :事务处理过程中的中间状态对外部是不可见的;
- durability(持久性) :事务完成之后,它对于数据的修改是永久性的。
并发事务可能存在的问题
- 脏读:读取未提交的事务。
- 不可重复读:多次读取同一数据,读取的数据不一致。
- 幻读:幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
事务隔离级别
- READ-UNCOMMITTED:一个事务还没提交时,它做的变更就能被别的事务看到。
- READ-COMMITTED:一个事务提交之后,它做的变更才会被其他事务看到。
- REPEATABLE-READ:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
- SERIALIZABLE:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
事务隔离的实现
数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
在“读提交”隔离级别下,这个视图是在每个SQL语句开始执行的时候创建的。
“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;
“串行化”隔离级别下直接用加锁的方式来避免并行访问。
事务可见性分析(RR)
事务启动瞬间,当前正在“活跃”的所有事务ID的最小值记为低水位,最大值加1记为高水位。
- row trx_id<低水位:这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
- 低水位<row trx_id<高水位:
- 在活跃数组中,表示这个版本是由还没提交的事务生成的,不可见;
- 不在活跃数组中,表示这个版本是已经提交了的事务生成的,可见。
- row trx_id>高水位:由将来启动的事务生成的,是肯定不可见的;
也可以从事务启动时间来看:
- 版本未提交,不可见;
- 版本已提交,但是是在视图创建后提交的,不可见;
- 版本已提交,而且是在视图创建前提交的,可见。
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)
select * from t where id=1加上lock in share mode 或for update,也是当前读。
Redo log
Redo log称为重做日志,用于记录事务操作变化,记录的是数据被修改之后的值。
Redo log 由两部分组成:
- 内存中的重做日志缓冲(redo log buffer)
- 重做日志文件(redo log file)
每次数据更新会先更新 redo log buffer,然后根据 innodb_flush_log_at_trx_commit 来控制 redo log buffer 更新到redo log file 的时机。innodb_flush_log_at_trx_commit 有三个值可选:
- 0:表示每次事务提交时都只是把redo log留在redo log buffer中,每隔一秒把log buffer刷到文件系统中去,并且调用文件系统的“flush”操作将缓存刷新到磁盘上去。
- 1(默认值):表示每次事务提交时都将redo log刷到文件系统中,并flush到磁盘;
- 2:表示每次事务提交时都只是把redo log刷到文件系统,但不flush到磁盘。
除了后台线程每秒一次的轮询操作外,还有两种场景会让一个没有提交的事务的redo log写入到磁盘中。
- redo log buffer占用的空间即将达到 innodb_log_buffer_size一半的时候,后台线程会主动写盘。
- 并行的事务提交的时候,顺带将这个事务的redo log buffer持久化到磁盘。
Binlog
二进制日志(binlog)记录了所有的 DDL(数据定义语句)和 DML(数据操纵语句)
Binlog 有以下几个作用:
- 恢复:数据恢复时可以使用二进制日志
- 复制:通过传输二进制日志到从库,然后进行恢复,以实现主从同步
- 审计:可以通过二进制日志进行审计数据的变更操作
sync_binlog 来控制累积多少个事务后才将二进制日志 fsync 到磁盘。
- sync_binlog=0,表示每次提交事务都只write,不fsync。文件系统决定什么时候fsync
- sync_binlog=1,表示每次提交事务都会执行fsync
- sync_binlog=N,表示每次提交事务都write,累积N个事务后才fsync
binlog格式
- statement:binlog里面记录的就是SQL语句的原文。可能会导致主备不一致。不太推荐使用
- row:binlog里面记录了真实删除行的主键id,不会有主备删除不同行的问题。缺点是很占空间。优点利于恢复数据。
- mixed格:MySQL自己判断SQL语句是否可能引起主备不一致,是就用row格式,否则就用statement格式。
redolog和binlog区别
redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
redo log是物理日志,记录的是“在某个数据页上做了什么修改”;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如“给ID=2这一行的c字段加1 ”。
redo log是循环写的,空间固定会用完;binlog是可以追加写入的。“追加写”是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
数据库突然断电不丢数据
只要 innodb_flush_log_at_trx_commit 和 sync_binlog 都为 1(通常称为:双一),就能确保MySQL 机器断电重启后,数据不丢失。
事务建议
- 循环写入的情况,如果循环次数不是太多,建议在循环前开启一个事务,循环结束后统一提交。
- 优化事务里的语句顺序,减少锁时间。
- 关注不同事务访问资源的顺序,避免死锁。
- 创建事务之前,关注事务隔离级别。
- 不在事务中混合使用存储引擎(MyISAM无法回滚)
分布式事务
分布式事务使用两阶段提交协议:
第一阶段:所有分支事务都开始准备,告诉事务管理器自己已经准备好了;
第二阶段:确定是 rollback 还是 commit,如果有一个节点不能提交,则所有节点都要回滚。
MySQL 自带的分布式事务
xa start 'a','a_1'; //启动分支事务
xa end 'a','a_1'; //结束分支事务
xa prepare 'a','a_1'; //进入准备状态
xa commit 'a','a_1'; //提交分支事务
xa recover; //返回当前数据库中处于 prepare 状态的分支事务的详细信息