6.恢复
innodb启动时不管上次是否正常关闭都会尝试进行回复操作
redo日志是物理日志,记录的是页的变化,比逻辑日志如binlog快很多
innodb顺序读取并行应用redo日志
checkpoint表示刷新到磁盘的lsn,在恢复过程中从checkpoint开始的日志部分,例如在checkoint的lsn为1 0000时宕机,恢复仅在1 0000 ~ 1 3000范围内的日志
幂等性:操作一次与重复多次操作的结果是一致的
redo日志是幂等的,记录的是页的改变,binlog不是幂等的,记录的是sql语句等mysql上层的操作
7.2.2 undo
1.概念
innodb在数据库修改过程中会产生undo日志,来用于回滚操作
undo日志放在undo段中(undo segment),位于共享表空间里面
undo不是将数据库物理地恢复到执行语句或事务之前的情况,只是将数据库逻辑地恢复到原来的样子,修改被逻辑地取消了,但数据结构和页本身在回滚后可能大不相同
在并发中,数十上千事务进行操作,一个事务在修改一个页的几条记录,另外事务在修改页的其他记录,不可能将页回滚到事务开始的样子
回滚中进行的是相反操作
insert <-> delete
update <-> update
同时MVVC也是通过undo实现的,当一行记录被其他事务锁定时,当前事务可以通过Undo读取之前的版本信息来实现非锁定读
undo也会产生undo log
2.undo存储管理
采用段管理,回滚段rollback segment,每个记录了1024个undo log segment,在每个undo log segment进行undo页的申请
共享表空间偏移量为 5 的页记录了所有rollback segment header所在的页,FIL_PAGE_TYPE_SYS
innodb现版本支持128个rollback segment,对在线事务的限制提高到128 * 1024
某些参数可以对rollback segment进行设置
- innodb_undo_directory
rollback segment所在的路径,即可以设置在共享表空间外的地方,默认为‘.‘,表示innodb的目录
- innodb_rollback_segments
设置rollback segment的个数,默认128
- innodb_undo_tablespaces
设置构成rollback segment文件的数量,使其较为评价分布在多个文件
事务在undo log segment分配页写入undo log过程同样需要写入重做日志
事务提交时:
- 将undo log放入列表,以供purge操作
- 判断undo log所在页是否可以重用,可以则分配给下一个事务
不能立即删除undo log及其所在的页,因为其他事务需要通过undo log实现MVVC来得到之前的版本,将undo log放入一个链表,又purge线程来判断
为事务单独分配一个单独undo页会浪费空间,对于OLTP(查询居多)类型,因为事务提交后不能立即释放页。
innodb对undo页进行重用:
事务提交时将其放入链表中,然后判断undo页空间是否小于3 / 4,是则可以被重用,之后的undo日志记录在当前页的undo日志后
3.undo log格式
undo lod分为
- insert undo log
insert操作产生的log,仅对本事务可见,在事务提交后可以直接删除,不需要purge操作
*表示对存储的字段进行了压缩
next:2字节,下一个undo log的位置
start:2字节,本undo log开始的位置
type_cmpl:1字节,undo类型,insert un log值为11
undo_no:事务的id
table_id:undo log对应的表对象
所有主键的列和值,进行回滚时定位到具体记录,删除
- update undo log
delete和update产生的log,可能需要提供MVVC机制,不能在提交时删除,提交时放入undo log链表,等待purge线程删除
next、start、undo_no、table_id和之前的insert undo log
type_cmpl的可能的值有:
- 12 TRX_UNDO_UPD_EXIST_REC
不更新主键,即不用删除记录,即non-delete-mark
- 13 TRX_UNDO_UPD_DEL_REC
将delete记录标记为not delete
- 14 TRX_UNDO_UPD_DEL_MARK_REC
将记录标为delete
记录update_vector信息,表示update操作改变的列,每个修改列的信息都要记录在undo log中,甚至可能记录对索引列做的修改
delete操作并不直接删除记录,而是将记录标记为已删除,即delete flag设置为1,最终由purge操作完成
更新操作涉及主键时,将原主键记录标记为已删除,产生trx_undo_del_mark_rec的undo log,之后插入新记录,产生rex_undo_insert_rec的undo log
7.2.3 Purge
delete和update可能并不直接删除原有数据,被purge延时删除,来支持MVVC
purge来判断是否可以删除该记录,当该行记录不被其他任何事物引用时则可以进行真正的delete操作
页中允许多个事务的undo log存在,但不代表着事务在全局过程中的顺序,innodb中有一个history列表,根据事务提交的顺序,将undo log进行连接
history按照事务提交的顺序将undo log组织,先提交的总在尾端
Purge执行时,先从history找到第一个需要被清理的记录,设为trx1,清理后会在页中继续寻找,发现了trx3、trx5,但trx5被其他事务使用,于是再回到histroy列表寻找,找到了trx2,再在页中寻找,清理了trx6,trx4
先在histroy list中寻找,再在undo page寻找,减少了大量的随机读取操作,提高效率
innodb_purge_batch_size设置了每次purge需要清理的数量
mysql> show variables like ‘innodb_purge_batch_size‘;
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_purge_batch_size | 300 |
+-------------------------+-------+
1 row in set, 1 warning (0.01 sec)
分配过大可能导致cpu和io集中undo log处理
当innodb压力大时不能高效进行purge操作,history的长度越来越长,innodb_max_purge_lag来控制长度,大于时则延缓DML操作,默认为0,不进行限制
mysql> show variables like ‘innodb_max_purge_lag‘;
+----------------------+-------+
| Variable_name | Value |
+----------------------+-------+
| innodb_max_purge_lag | 0 |
+----------------------+-------+
1 row in set, 1 warning (0.00 sec)
当数值大于0时,延缓算法为:
delay = ((length(history_list) - innodb_max_purge_log) * 10) - 5
单位毫秒,对象是行,在每次purge操作后会重新计算
当delay值大于innodb_max_purge_lag_delay时则设置为其值,避免purge缓慢导致sql线程无限制等待
7.2.4 group commit
非只读事务在提交时要进行fsync操作,来保证redo日志都写入磁盘,当数据库宕机时,可以通过redo日志恢复
fsync的性能有限的,于是出现了group commit功能,即一个fsync可以刷新多个事务的日志写入磁盘
innodb提交事务:
- 修改内存事务的信息,将日志写入redo日志缓冲
- fsync将日志从日志缓冲写入磁盘
第二步相比第一步速度慢,所以当有事务在进行第二步时其他事务可以进行第一步,之后再进行第二步,于是就是将多个事务的redo日志通过一次fsync写入多个事务的redo日志,对写入或更新频繁的事务,group commit效果明显
当binlog开启时group commit会失效,且运行环境会为了replication复制情况开启binlog
原因是为了保证存储引擎层的事务和binlog一致,使用了两阶段事务:
- (1)事务提交时innodb进行prepare操作
- (2)mysql数据库上层写入binlog日志
- (3)innodb将日志写入redo日志文件
- (a)修改内存事务的信息,将日志写入redo日志缓冲
- (b)fsync将日志从日志缓冲写入磁盘
当mysql数据库上层写入binlog日志后,事务就提交了,即使innodb将日志写入redo日志文件发生了宕机
且每个步骤都要进行fsync操作来保证上下层的一致性
(2)由sync_binlog控制,(3)由innodb_flush_log_at_trx_commit控制
mysql> show variables like ‘sync_binlog‘;
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| sync_binlog | 1 |
+---------------+-------+
1 row in set, 1 warning (0.01 sec)
mysql> show variables like ‘innodb_flush_log_at_trx_commit‘;
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
1 row in set, 1 warning (0.00 sec)
mysql使用了 pre_commit_mutex锁来保证binlog写入顺序和innodb事务提交顺序一致,锁开启后,(3)(a)不可以在其他事务执行(b)时进行,因此group commit失效
binlog写入顺序和innodb事务提交顺序一致是为了保证可以建立副本replication
事务T1-T3原本是按顺序进行的,但恢复时T3进行了提交了,默认前面事务完成了提交,于是T1未提交的数据则丢失了
加锁来保证事务执行顺序
改进:
Binary Log Group COmmit(BLGC)
将事务分成几个步骤来提交:
数据库上层提交时按顺序放入队列中,第一个事务称leader,其他为follower,leader控制其他,步骤如下:
- flush阶段:将binlog写入内存
- sync阶段:将binlog写入到磁盘,队列中多个事务经过一个fsync操作写入(BLGC)
- commit阶段:leader根据顺序调用innodb事务的提交,于是使用group commit
当一组事务在commit时,其他事务可以进行flush,使group commit生效
binlog_max_flush_queue_time控制flush阶段等待时间,即前一组事务提交,后一组事务不立即进入sync阶段,而是等待一段时间,使group commit更多事务,但相应事务响应时间变长,默认0,推荐0,除非存在大量连接不断地写入或更新
7.3 事务控制语句
- start transaction | begin
显式开启事务
- commit
提交事务
- rollback (work)
回滚事务
- savepoint idntifier
设置保存点,可以有多个
- release savepoint identifier
删除保存点
- rollback to identifier
回滚到某个标记点
- set transaction
设置事务隔离级别
存储过程开启事务只能使用 start transaction
completion_type控制commit work 与commit 是否等价
为0表示等价
为1 commit work 表示 commit and chain,链事务,自动开启相同隔离几倍的事务
为2 表示 commit and release,断开和数据库的连接
在使用rollback to identifier回到保存点之后事务还没有提交,需要提交或者回滚会开始前
7.4 隐式提交sql语句
某些语句会隐式提交事务
- DDL语句:
- 隐式修改mysql架构:
create | drop | rename user、grant、revoke、set password
- 管理语句:
analyze table、cache index、check table、load index into cache、optimize table、repair table
truncate table是DDL,与删除同效,但不能被回滚
7.5 对事务操作的统计
每秒请求数:Question Per Second,QPS
每秒事务处理能力:Transacion Per Second,TPS
TPS:(com_commit + com_rollback) / time,事务显示提交
mysql> show global status like ‘com_commit‘;
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_commit | 0 |
+---------------+-------+
1 row in set (0.00 sec)
mysql> show global status like ‘com_rollback‘;
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Com_rollback | 0 |
+---------------+-------+
1 row in set (0.00 sec)
7.6 事务的隔离级别
- read uncommitted读未提交
- read committed读已提交
- repeatable read可重复读
- serializable
设置当前会话或者全局事务隔离级别
set [global | session] transaction isolation level {
read uncommitted |
read committed |
repeatable read |
serializable
}
在配置文件中设置
transaction-isolation = read-committed
查看当前会话级别
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
mysql> SELECT @@global.transaction_isolation;
+--------------------------------+
| @@global.transaction_isolation |
+--------------------------------+
| REPEATABLE-READ |
+--------------------------------+
1 row in set (0.00 sec)
serialiable隔离级别下,innodb对每个select语句自动加上lock in share mode,每个读加一个共享锁,对一致性非锁定读不支持了,主要用于分布式事务
read committed隔离级别下除了唯一性的约束检查及外键约束需要gap lock外不会使用此锁算法