事务
ACID
A是atomicity,原子性,事务内的操作要么全部完成,要么全部失败。通过undo log实现。
C是consistency,一致性。一致性是通过什么保证的?
I是isolation,隔离性,多事务之间互不干扰。通过MVCC+锁实现。
D是durability,持久性,事务提交后,数据不丢。通过redo log实现。
隔离级别有4种,由低到高,依次为Read Uncommitted、Read Committed、Repeatable read、Serializable。
Read Uncommitted
读未提交。这种隔离级别下有脏读、不可重复读和幻读问题。
脏读问题指的是,一个事务可以读到另一个未提交的事务的操作。
不可重复读指的是,A事务先读取了一下,B事务修改了这条数据,并提交了。这个时候A再去读,发现数据变了。
幻读指的是,A事务先读取了一下,B事务新增或删除了一条满足A事务查询条件的记录,A事务再去读,发现多或少了一条记录。
Read Committed
读已提交。每次查询都读最新已提交的数据。这种隔离级别下没有脏读问题,但是会有不可重复读和幻读问题。
Repeatable Read
可重复读。这种隔离级别下没有脏读、不可重复读问题,但是会有幻读问题。然而,mysql数据库利用MVCC机制解决了快照读的幻读问题,利用行锁、间隙锁、临建锁解决了当前读的幻读问题。
Serializable
这种隔离级别下,事务不能并发,不会有任何问题,但是降低了数据库的性能。
mysql的mvcc机制
mvcc的全称是multiversion concurrency control,中文翻译为多版本并发控制。
mvcc本质是通过undo log维护多版本数据,通过read view进行可见性判断,在快照读场景下实现无锁读,从而提高并发性能。mvcc有三要素:版本链、read view、快照读。
版本链
每一行数据都有两个隐藏字段trx_id和roll_pointer,trx_id存储的是最近修改该行的事务id,roll_pointer指向旧版本数据,旧版本数据在undo log中。而undo log中的记录也有roll_pointer,指向更旧的版本。由此就形成了版本链,新版本->旧版本->更旧版本。
read view
read view决定哪个版本能被当前事务看到。创建read view时,会记录4个关键值:
m_ids,表示当前活跃的事务id列表
min_trx_id,表示最小的活跃事务id
max_trx_id,表示系统将要分配的下一个事务id
creator_trx_id,表示创建当前read view的事务id
所谓可见性判断,就是从新到旧遍历版本链,找到第一个对当前read view可见的版本。
假如当前read view的m_ids是[100, 101, 102],min_trx_id是100,max_trx_id是103,creator_trx_id是102
某一行数据/某一条undo log记录,对当前read vew是否可见的判断规则是:
①如果行的trx_id<min_trx_id,则说明修改这一行的事务早就提交了,可见,创建快照。
②如果行的trx_id>=max_trx_id,则说明这一行的修改是之后的事务操作的,不可见,接着去判断旧版本。
③如果行的trx_id等于creator_trx_id,则说明是这一行的修改是当前事务操作的,可见,创建快照。
④如果行的trx_id在m_ids中,则说明修改这一行的事务还未提交,不可见,接着去判断旧版本。
⑤其他情况,即trx_id在min_trx_id~max_trx_id之间,但不在m_ids中,说明修改这一行的事务早就提交了,可见,创建快照。
快照读
如select * from t where id=1,这种普通的select是快照读。
mvcc只作用于快照读。当前读不用mvcc,而是用锁。
如select * from t where id=1 for update;
select * from t where id=1 lock in share mode;
update t set score=100 where id=1;
delete from t where id=1;
update、delete以及加锁的select(for update是排他锁,lock in share mode是共享锁)都是当前读。当前读会加锁,每次读的都是最新数据。
在read committed隔离级别下,快照读由于每次都会生成新的read view,因此可能会出现不可重复读和幻读问题。而当前读会对已读取的记录加行锁,因此可以避免不可重复读,但由于不会加间隙锁,所以仍然可能出现幻读。
在repeatable read隔离级别下,快照读通过在第一次select时创建read view,并在整个事务中复用,从而保证读取结果一致,因此不会出现不可重复读和幻读。而对于当前读,InnoDB会通过行锁锁住记录、在范围查询场景下则是使用临键锁锁住记录及其间隙(临键锁就是行锁+间隙锁),防止其他事务修改或插入,从而避免不可重复读和幻读。
和事务、锁相关的有三个表
information_schema.innodb_trx
performance_schema.data_lock_waits
performance_schema.data_locks
information_schema.innodb_trx表记录的是当前活跃的事务,performance_schema.data_lock_waits记录的是谁在等谁,performance_schema.data_locks记录的是锁的具体信息。
mysql服务层log主要有慢查询日志和binlog,存储引擎层日志(InnoDB引擎)主要有undo log和redo log。
服务层日志
慢查询日志记录的是执行时间超过阈值的sql,用于定位问题。
binlog主要作用是数据复制,如主从同步。
InnoDB存储引擎层日志
undo log记录的是数据修改前的旧值,用于事务回滚和MVCC(多版本并发控制)。
比如有一个事务,要修改id=1的记录
begin;
update account set balance=50 where id=1;
执行update时,会先记录undo log再修改数据。这样,如果执行rollback,就会根据undo log恢复数据。
mvcc用于实现读写并发时,不加锁也能读到历史数据,即快照读。
redo log用于保证事务的持久性,崩溃恢复。
以事务中执行update操作为例,执行流程是:修改buffer pool中的数据页,产生脏页。写入redo log buffer。
事务提交后,redo log刷盘,返回成功。即提交成功,代表redo log已经落盘。
而脏页刷盘是异步的,由后台线程处理。
redo log记录的是物理页的修改,是幂等的。mysql崩溃重启时,会先通过redo log重做已提交但未落盘的数据页,然后再通过undo log回滚未提交事务。崩溃恢复=redo+undo。
双1配置
sync_binlog,控制的是bin log的刷盘机制。值为1,表示事务提交时就刷盘。
innodb_flush_log_at_trx_commit,控制的是redo log的刷盘机制。值为1,表示事务提交时就刷盘。
浙公网安备 33010602011771号