nnodb支持的回滚段总共有128*1024=131072个。
在事务的执行过程中,会产生两种回滚日志,一种是insert的undo日志,一种是update的undo日志。可能有人会问,delete哪里去了。其实是包含在update回滚日志中的。
因为innodb把undo分为两类:一类是新增,也就是insert,一类是修改,也就是update。分类的依据就是commit后要不要做purge操作,因为insert是不需要purge的,只要事务提交了,那个回滚日志就可以丢掉了;而对于更新和删除操作,如果事务提交了,还需要为MVCC服务,那就需要将这些日志放到history list中去,等待去做purge或MVCC的多版本查询等。
通常情况下,如果一个事务既有插入,又有更新(或删除),那么这个事务对应两个UNDO段,即在一个rseg的1024个槽中,要使用两个槽来存储事务的回滚段,一个是插入段,一个是更新段。
在事务要存储回滚记录的时候,事务就要从1024个槽中,根据相应的更新类型(插入或者更新)找到空闲的槽做为自己的UNDO段。如果已经申请过相同类型的undo段,就直接使用,否则就需要创建一个段,并将段首页号写入这个rseg长度为1024的数组多赢的空闲位置中去。
如果在1024个槽中,找不到空间的位置,那么这个事务就被回滚掉,报出错误“too many active concurrent transactions”,错误号为1637的异常。当然,这种情况一般不会见到,如果能把这个用完,估计数据库已经根本动不了了。
回滚时刻:在数据库的启动过程中,先进行redo日志恢复,然后才进行undo的操作。
mysql是根据innodb_force_recovery来决定要不要做回滚操作,如果设置为3或3以上,就不需要回滚了,这可能会导致数据库逻辑上的不一致。
因为undo是反向操作,所以应该是先处理新产生的事务,后处理老的事务,通过事务号区分新老关系。也就是说,innodb是通过从大到小的顺序遍历。
针对每一个undo段,innodb会将所有的状态为ACTIVE的事务的undo日志扫描出来,然后一条一条的做回滚操作。
InnoDB支持128个undo logs,这里特别说明下,从5.7开始,innodb_rollback_segments的名字改成了innodb_undo_logs,但表示的都是回滚段的个数。
我们先大致看下InnoDB的undo在不同的版本上的一些演进:
MySQL 5.5的版本上
InnoDB undo是放在系统表空间即ibdata file文件中,这样如果有比较大的事务(即需要生成大量undo的),会撑大ibdata数据文件,
虽然空间可以重用, 但文件大小不能更改。
关于回滚段的,只有这个主要的参数,用来设置多少个rollback segment。
[html] view plain copy
mysql> show global variables like '%rollback_segment%';
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| innodb_rollback_segments | 128 |
+----------------------------+-------+
MySQL 5.6的版本上
InnoDB undo支持独立表空间, 增加如下参数:
[html] view plain copy
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_undo_directory | . |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 1 |
+-------------------------+-------+
这样,在install的时候,就会在data目录下增加undo数据文件,来组成undo独立表空间,但文件变大之后的空间回收还是成为问题。
MySQL 5.7的版本上
InnoDB undo在支持独立表空间的基础上,支持表空间的truncate功能,增加了如下参数:
[html] view plain copy
mysql> show global variables like '%undo%'; +--------------------------+------------+
| Variable_name | Value |
+--------------------------+------------+
| innodb_max_undo_log_size | 1073741824 |
| innodb_undo_directory | ./ |
| innodb_undo_log_truncate | OFF |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 3 |
+--------------------------+------------+
mysql> show global variables like '%truncate%';
+--------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------+-------+
| innodb_purge_rseg_truncate_frequency | 128 |
| innodb_undo_log_truncate | OFF |
+--------------------------------------+-------+
InnoDB的purge线程,会根据innodb_undo_log_truncate开关的设置,和innodb_max_undo_log_size设置的文件大小阈值,以及truncate的频率来进行空间回收和rollback segment的重新初始化。
注释:
innodb_undo_log_truncate参数设置为1,即开启在线回收(收缩)undo log日志文件,支持动态设置。
innodb_undo_tablespaces参数必须大于或等于2,即回收(收缩)一个undo log日志文件时,要保证另一个undo log是可用的。
innodb_undo_logs: undo回滚段的数量, 至少大于等于35,默认128。
innodb_max_undo_log_size:当超过这个阀值(默认是1G),会触发truncate回收(收缩)动作,truncate后空间缩小到10M。
innodb_purge_rseg_truncate_frequency:控制回收(收缩)undo log的频率,表示purge undo轮询128次后,进行一次undo的truncate。undo log空间在它的回滚段没有得到释放之前不会收缩,想要增加释放回滚区间的频率,就得降低innodb_purge_rseg_truncate_frequency设定值。
innodb_undo_directory:undo文件存放的位置;
默认情况下, 是purge触发128次之后,进行一次rollback segment的free操作,然后如果全部free就进行一个truncate。
当设置innodb_undo_log_truncate=ON的时候, undo表空间的文件大小,如果超过了innodb_max_undo_log_size, 就会被truncate到初始大小,但有一个前提,就是表空间中的undo不再被使用。
注意:
如果是在线库,要注意影响,因为当一个undo tablespace在进行truncate的时候,不再承担undo的分配。只能由剩下的undo 表空间的rollback segment接受事务undo空间请求。
MySQL 5.7 设置undo为独立表空间
MySQL5.7中开始支持把undo log分离到独立的表空间,并放到单独的文件目录下;采用独立undo表空间,再也不用担心undo会把 ibdata1 文件搞大,还可以自动收缩空间;
也给我们部署不同IO类型的文件位置带来便利,对于并发写入型负载,我们可以把undo文件部署到单独的高速存储设备上.
在数据库初始化的时候就使用如下几个参数,就可以分离出单独的undo表空间
(root@localhost)[(none)]> show variables like '%undo%';
+--------------------------+--------------+
| Variable_name | Value |
+--------------------------+--------------+
| innodb_max_undo_log_size | 104857600 |
| innodb_undo_directory | /log/undolog |
| innodb_undo_log_truncate | ON |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 3 |
+--------------------------+--------------+
5 rows in set (0.01 sec)
(root@localhost)[(none)]> show variables like '%truncate%';
+--------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------+-------+
| innodb_purge_rseg_truncate_frequency | 128 |
| innodb_undo_log_truncate | ON |
+--------------------------------------+-------+
show status like 'Innodb_available_undo_logs';
无法动态修改:
root@localhost [(none)]> set global innodb_undo_tablespaces=3;
ERROR 1238 (HY000): Variable 'innodb_undo_tablespaces' is a read only variable
已安装数据库不能修改 innodb_undo_tablespaces
2016-03-24 16:27:18 7164 [Note] InnoDB: Using Linux native AIO
2016-03-24 16:27:18 7164 [Note] InnoDB: Using CPU crc32 instructions
2016-03-24 16:27:18 7164 [Note] InnoDB: Initializing buffer pool, size = 100.0M
2016-03-24 16:27:18 7164 [Note] InnoDB: Completed initialization of buffer pool
2016-03-24 16:27:18 7fb591068720 InnoDB: Expected to open 3 undo tablespaces but was able
2016-03-24 16:27:18 7fb591068720 InnoDB: to find only 0 undo tablespaces.
2016-03-24 16:27:18 7fb591068720 InnoDB: Set the innodb_undo_tablespaces parameter to the
2016-03-24 16:27:18 7fb591068720 InnoDB: correct value and retry. Suggested value is 0
2016-03-24 16:27:18 7164 [ERROR] Plugin 'InnoDB' init function returned error.
2016-03-24 16:27:18 7164 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
2016-03-24 16:27:18 7164 [ERROR] Unknown/unsupported storage engine: InnoDB
2016-03-24 16:27:18 7164 [ERROR] Aborting