5.7新特性

1. 背景

MySQL 5.7在2015-10-21发布了GA版本,即5.7.9,目前小版本已经到了5.7.12。5.7新增了许多新的feature和优化,接下来一个系列,我们就一起来尝尝鲜。首先这次主要是预览feature的变化以及兼容性问题。后面的系列,会针对重要的feature展开来学习。

2 安全相关的特性

2.1 认证插件

mysql.user表中的plugin更改成not null,5.7开始不再支持mysql_old_password的认证插件,推荐全部使用mysql_native_password。从低版本升级到5.7的时候,需要处理两个兼容性问题。

[兼容性] 需要先迁移mysql_old_password的用户,然后进行user表结构的升级:

1. 迁移mysql_old_password用户 MySQL 5.7.2之前的版本,是根据password的hash value来判断使用的认证插件类型,5.7.2以后的版本,plugin字段为not null,就直接根据plugin来判断了。新的密码从password字段中,保存到新的字段authentication_string中,password字段废弃处理。

如果user是隐式的mysql_native_password。直接使用sql进行变更:

UPDATE mysql.user SET plugin = ‘mysql_native_password‘ WHERE plugin = ‘‘ AND (Password = ‘‘ OR LENGTH(Password) = 41);
FLUSH PRIVILEGES;

如果user是隐式的或者显示的mysql_old_password, 首先通过以下sql进行查询:

SELECT User, Host, Password FROM mysql.user WHERE (plugin = ‘‘ AND LENGTH(Password) = 16) OR plugin = ‘mysql_old_password‘;

如果存在记录,就表示还有使用mysql_old_password的user,使用以下sql进行用户的迁移:

ALTER USER ‘user1‘@‘localhost‘ IDENTIFIED WITH mysql_native_password BY ‘DBA-chosen-password‘;

2. user表结构升级 通过mysql_upgrade直接进行升级,步骤如下[5.6->5.7]:

  1. stop MySQL 5.6实例
  2. 替换5.7的mysqld二进制版本
  3. 使用5.7启动实例
  4. run mysql_upgrade升级系统表
  5. 重启MySQL 5.7实例

2.2 密码过期

用户可以通过 ALTER USER ‘jeffrey‘@‘localhost‘ PASSWORD EXPIRE;这样的语句来使用户的密码过期。 并新增加 default_password_lifetime来表示用户密码自动过期时间,从5.7.10开始,其默认值从0变更到了360,也就是默认一年过期。 可以通过以下两种方法禁止过期:

1. SET GLOBAL default_password_lifetime = 0;
2. ALTER USER ‘jeffrey‘@‘localhost‘ PASSWORD EXPIRE NEVER;

[兼容性] 只需要通过mysql_upgrade升级mysql.user系统表就可以使用密码过期新功能。

2.3 账号锁定

用户可以通过以下语法进行账号锁定,阻止这个用户进行登录:

ALTER USER ‘jeffrey‘@‘localhost‘ ACCOUNT LOCK;
ALTER USER ‘jeffrey‘@‘localhost‘ ACCOUNT UNLOCK;

[兼容性] 只需要通过mysql_upgrade升级mysql.user系统表就可以使用密码过期新功能。

2.4 SSL连接

如果mysqld编译使用的openssl,在启动的时候,默认创建SSL, RSA certificate 和 key 文件。 但不管是openssl还是yassl,如果没有设置ssl相关的参数,mysqld都会在data directory里查找ssl认证文件,来尽量打开ssl特性。

[兼容性] 不存在兼容性的问题

2.5 安装数据库

5.7开始建议用户使用 mysqld --initialize来初始化数据库,放弃之前的mysql_install_db的方式,新的方式只创建了一个[email protected]的用户,随机密码保存在~/.mysql_secret文件中,并且账号是expired,第一次使用必须reset password,并且不再创建test db。

[兼容性] 不存在兼容性的问题

3 sql mode变更

5.7 sql_mode的默认值变更为:

mode_no_engine_substitution |
      mode_only_full_group_by |
      mode_strict_trans_tables |
      mode_no_zero_in_date |
      mode_no_zero_date |
      mode_error_for_division_by_zero |
      mode_no_auto_create_user

而在5.7之前,sql_mode的默认值都只有mode_no_engine_substitution。 所以在5.7默认的情况下,比如grant不存在的用户的时候,会报一下错误:

ERROR 1133 (42000): Can‘t find any matching row in the user table

必须先使用create user,然后再使用grant user。

[兼容性] 默认sql mode发生变更会导致sql的行为不一致。

4. online alter table

支持online rename index操作, in_place并且不需要table copy。 [兼容性] 不存在兼容性的问题

5. InnoDB增强

5.1 varchar长度变更支持inplace

变更varchar 类型字段的长度支持inplace方法,但有一个限制,即用于表示varchar字段长度的字节数不能发生变化,也就是支持比如varchar的长度在255以下变更或者255以上的范围进行变更,因为从小于255变更到大于255,其size的字节需要从1个增加到2个。

注意:减少varchar的长度,仍然需要table copy。

5.2 优化InnoDB临时表

因为InnoDB临时表的数据不再不受redo保护,而redo只保护临时表的元数据,所以大幅提升了临时表的性能。 并且InnoDB临时表的元数据保存在一个新的系统表中即innodb_temp_table_info, 临时表将建立一个统一的表空间,我们称之为临时表空间,其目录地址可以通过参数innodb_temp_data_file_path来设置。系统在启动的时候,都会新建这个表空间,重启会删除重建。

例如:

mysql> show global variables like ‘%temp_data_file_path%‘;
+----------------------------+-----------------------+
| Variable_name              | Value                 |
+----------------------------+-----------------------+
| innodb_temp_data_file_path | ibtmp1:12M:autoextend |
+----------------------------+-----------------------+

并且5.7存储引擎默认都变更成InnoDB了:

mysql> show global variables like ‘%storage_engine%‘;
+----------------------------------+--------+
| Variable_name                    | Value  |
+----------------------------------+--------+
| default_storage_engine           | InnoDB |
| default_tmp_storage_engine       | InnoDB |
| disabled_storage_engines         |        |
| internal_tmp_disk_storage_engine | InnoDB |
+----------------------------------+--------+

注意: 在开启gtid的情况下,非auto commit或者显示begin的context下,create 或者drop 临时表,仍然和5.6一样:

ERROR 1787 (HY000): Statement violates GTID consistency: CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE can only be executed outside transactional context.

另外, insert into t select * from t也会遇到错误,不能在一个sql语句中reference两次临时表。

备注: 因为InnoDB临时表进行了比较大的变动,我们会专门进行一次详细的介绍。

5.3 InnoDB原生支持DATA_GEOMETRY类型

并且支持在spatial data types上建立index,加速查询。

5.4 buffer pool dump

buffer pool dump和load支持一个新的参数innodb_buffer_pool_dump_pct,即dump的比例,并且使用innodb_io_capacity 来控制load过程中的IO吞吐量。

5.5 多线程flush dirty

从5.7.4开始,innodb_page_cleaners参数可以设置,支持多线程flush dirty page,加快脏块的刷新。

5.6 NVM file system

MySQL 一直使用double write buffer来解决一个page写入的partial write问题,但在linux系统上的Fusion-io Non-Volatile Memory (NVM) file system支持原子的写入。 这样就可以省略掉double write buffer的使用, 5.7.4以后,如果Fusion-io devices支持atomic write,那么MySQL自动把dirty block直接写入到数据文件了。这样减少了一次内存copy和IO操作。

5.7 InnoDB分区表

MySQL 5.7之前的版本,InnoDB并不支持分区表,分区表的支持是在ha_partition引擎上支持的,从5.7开始,InnoDB支持原生的分区表,并且可以使用传输表空间。

[兼容性] mysql_upgrade会扫描ha_partition引擎支持的InnoDB表,并升级成InnoDB分区表,5.7.9之后,可以通过命令ALTER TABLE … UPGRADE PARTITIONING.进行升级。如果之前的版本大量使用了分区表,要注意使用mysql_upgrade会消耗非常长的时间来升级分区表。

5.8 动态调整buffer pool size

MySQL 5.7.5之后,可以online动态调整buffer pool size,通过设置动态的参数innodb_buffer_pool_size来调整,并且根据Innodb_buffer_pool_resize_status状态来查看resize的进度,因为resize的过程是以chunk为大小,把pages从一个内存区域copy到另一片内存的。

5.9 加快recovery

MySQL 5.7.5之前,在recovery的过程中,需要扫描所有的ibd文件,获取元信息, 5.7.5之后,新加了一种redo log类型,即MLOG_FILE_NAME, 记录从上一次checkpoint以来,发生过变更的文件,这样在recovery的过程中,只需要打开这些文件就可以了。 [兼容性] 因为增加了新的log record type,需要安全的关闭5.7之前的实例,清理掉redo。

5.10 表空间管理

支持创建表空间,例如

CREATE TABLESPACE `tablespace_name`
ADD DATAFILE ‘file_name.ibd‘
[FILE_BLOCK_SIZE = n]

并可以在创建表的时候,指定属于哪个表空间,

[兼容性] 因为可以任意指定空间目录,要注意升级过程中,不要漏掉目录。

5.11 InnoDB Tablespace Encryption

支持InnoDB数据文件加密,其依赖keyring plugin来进行秘钥的管理,后面我们单独来介绍InnoDB加密的方法,并且RDS也实现了一种InnoDB数据文件透明加密方法,并通过KMS系统来管理秘钥。例如:

create table t(id int) encryption=‘y‘;

##########################################

继上一期月报,MySQL5.7新特性之一介绍了一些新特性及兼容性问题后,本期继续进行学习。

1. 系统变量

5.7以后System and status 变量需要从performance_schema中进行获取,information_schema仍然保留了GLOBAL_STATUS,GLOBAL_VARIABLES两个表做兼容。

[兼容性]
如果希望沿用information_schema中进行查询的习惯,5.7提供了show_compatibility_56参数,设置为ON可以兼容5.7之前的用法,否则就会报错:

ERROR 3167 (HY000): The ‘INFORMATION_SCHEMA.GLOBAL_STATUS‘ feature is disabled; see the documentation for ‘show_compatibility_56‘

5.7.6之后,在performance_schema新增了如下的表:

performance_schema.global_variables
performance_schema.session_variables
performance_schema.variables_by_thread
performance_schema.global_status
performance_schema.session_status
performance_schema.status_by_thread
performance_schema.status_by_account
performance_schema.status_by_host
performance_schema.status_by_user

5.7.9之前,需要有SELECT_ACL权限才能进行show查询,但5.7.9之后,默认这些表是不需要任何权限就可以访问了。

2. sys schema

新增了sys数据库,主要是performance_schema收集的信息,帮助DBA和开发人员方便诊断问题。 sys下的一共包括三种对象:1. view,2. procedure 3 function 这些对象都是基于performance_schema下的表,进行了可读性的聚合,没有真正存储数据,只存储了定义。

[兼容性]
mysql_install_db可以选择–skip-sys-schema跳过安装过程, 但默认mysql_upgrade会帮你创建sys下面的对象。不存在兼容性的问题

3. 异常栈

5.7开始支持异常诊断栈信息,通过GET STACKED DIAGNOSTICS可以获取栈内的信息。
具体的使用方法参考:https://dev.mysql.com/doc/refman/5.7/en/diagnostics-area.html

4. Triggers

支持在一个table对象上建多个trigger。

5. Generated Columns

5.7.6开始,支持生成列,这个列可以是虚拟的列,也可以是实体存储数据的列。
比如:

CREATE TABLE triangle (
  sidea DOUBLE,
  sideb DOUBLE,
  sidec DOUBLE AS (SQRT(sidea * sidea + sideb * sideb))
);

VIRTUAL: 表示这个字段是虚拟列,并不进行存储,查询的时候,通过计算得到
STORED: 需要存储空间,并且可以被索引的列

6. exchange partition不验证

这个是在oracle分区表上支持的功能,dba在做大表维护的时候,非常有用。

语法: ALTER TABLE ... EXCHANGE PARTITION WITHOUT VALIDATION

如果不验证,那么只有元数据信息的更改,就可以完成exchange,否则,就需要读取每一行数据进行验证,维护时间将根据这个表大小有关系。

7. dump线程增强

5.7.2之前,master dump线程需要持有LOCK_log锁去读取binlog然后发送到备库,而这时会阻塞client端去写入binlog。5.7.2之后,dump线程只需要持有LOCK_binlog_end_pos这个锁去读取binlog的当前的位置,来决定是否发送到备库去,这样就可以做到不阻塞任何binlog的写入。

8. 多源复制

多源复制可以从多个master复制到一个slave端,在数据库集群进行扩容和缩容的时候,非常有用。我们会在后面的系列单独来介绍。

9. 在线更改replication master

可以不用stop slave,然后在线更改replication master信息。 但这里并不是不需要slave停掉, 而是change master涉及到几个动作:

  1. 如果只是更改当前relay的信息,那么只需要sql线程是不工作的就可以了,IO thread可以继续
  2. 如果只是更改主库的信息,那么只需要IO线程不工作就可以了。 sql thread可以继续
  3. 如果需要重新启动主库和备库的恢复信息,比如master_auto_positioin=1,那么就需要IO和sql线程都停掉。

10. Group Replication

并行复制支持按照主库组提交的形式在备库进行回放。下一个系列进行单独来介绍

下面单独介绍一下MySQL 5.7对临时表进行的改动。

1. 背景

MySQL包括两类临时表,一类是通过create temporary table创建的临时表,一类是在query过程中using temporary而创建的临时表。 5.7之前,using temporary创建的临时表,默认只能使用myisam引擎,而在5.7之后,可以选择InnoDB引擎来创建。

临时表的引擎选择使用下面的这两个参数来决定:

mysql> show global variables like ‘%tmp%‘;
+----------------------------------+---------------------------------------+
| Variable_name                    | Value                                 |
+----------------------------------+---------------------------------------+
| default_tmp_storage_engine       | InnoDB                                |
| internal_tmp_disk_storage_engine | InnoDB                                |

2. 临时表空间

5.7之后,使用了独立的临时表空间来存储临时表数据,但不能是压缩表。临时表空间在实例启动的时候进行创建,shutdown的时候进行删除。

例如如下的配置:

mysql> show global variables like ‘%innodb_temp%‘;
+----------------------------+-----------------------+
| Variable_name              | Value                 |
+----------------------------+-----------------------+
| innodb_temp_data_file_path | ibtmp1:12M:autoextend |
+----------------------------+-----------------------+

create temporary table和using temporary table将共用这个临时表空间。

3. 临时表优化

临时表会伴随着大量的数据写入和读取,尤其是internal_tmp_table。所以,InnoDB专门对临时表进行了优化。
InnoDB使用如下两个标示临时表:

dict_tf2_temporary: 表示普通临时表
dict_tf2_intrinsic: 表示内部临时表

这两个标示,会在IBD文件的segment header占用两个bit位。intrinsic一定是temproary,也就是temproary上进行的优化 完全适用于intrinsic表上。

下面来看下具体的优化:

3.1. redo

临时表在连接断开或者数据库实例关闭的时候,会进行删除,所以,临时表的数据不需要redo来保护,即recovery的过程中 不恢复临时表,只有临时表的metadata使用了redo保护,保护元数据的完整性,以便异常启动后进行清理工作。

临时表的元数据,5.7之后,使用了一个独立的表进行保存,这样就不要使用redo保护,元数据也只保存在内存中。 但这有一个前提,必须使用共享的临时表空间,如果使用file-per-table,仍然需要持久化元数据,以便异常恢复清理。

3.2 undo

temporary table仍然需要语句级的回滚,所以,需要为数据生成undo。但intrinsic table不需要回滚,所以,intrinsic table 减少了undo的生成,性能更高。

3.3 lock

因为临时表只有本线程可以看见,所以减少了InnoDB的加锁过程。

可以看下insert的时候,进行的分支判断:

 row_insert_for_mysql(
	const byte*		mysql_rec,
	row_prebuilt_t*		prebuilt)
{
	/* For intrinsic tables there a lot of restrictions that can be
	relaxed including locking of table, transaction handling, etc.
	Use direct cursor interface for inserting to intrinsic tables. */
	if (dict_table_is_intrinsic(prebuilt->table)) {
		return(row_insert_for_mysql_using_cursor(mysql_rec, prebuilt));
	} else {
		return(row_insert_for_mysql_using_ins_graph(
			mysql_rec, prebuilt));
	}
}

row_insert_for_mysql_using_cursor直接跳过了加锁的lock_table过程。

然后,如果是intrinsic table,就直接插入,减少了undo的生成。
如果不是,需要加lock,并生成undo信息。

if (dict_table_is_intrinsic(index->table)) {

			index->rec_cache.rec_size = rec_size;

			*rec = page_cur_tuple_direct_insert(
				page_cursor, entry, index, n_ext, mtr);
		} else {
			/* Check locks and write to the undo log,
			if specified */
			err = btr_cur_ins_lock_and_undo(flags, cursor, entry,
							thr, mtr, &inherit);

插入的时候,如果是临时表。就关闭redo的生成。如下面的代码所示:

if (dict_table_is_temporary(index->table)) {
		/* Disable REDO logging as the lifetime of temp-tables is
		limited to server or connection lifetime and so REDO
		information is not needed on restart for recovery.
		Disable locking as temp-tables are local to a connection. */

		ut_ad(flags & BTR_NO_LOCKING_FLAG);
		ut_ad(!dict_table_is_intrinsic(index->table)
		      || (flags & BTR_NO_UNDO_LOG_FLAG));

		mtr.set_log_mode(MTR_LOG_NO_REDO);
	}

未完待续,下一个系列,我们将介绍一下undo的新特性,包括online truncated undo。

###############################################################################################

继上两期月报,MySQL5.7新特性之一介绍了一些新特性及兼容性问题,MySQL 5.7新特性之二介绍了临时表的优化和实现。
这期我们一起来学习下undo空间管理,重点介绍truncate功能。

1. 背景

InnoDB存储引擎中,undo在完成事务回滚和MVCC之后,就可以purge掉了,但undo在事务执行过程中,进行的空间分配如何回收,就变成了一个问题。 我们亲历用户的小实例,因为一个大事务,导致ibdata file到800G大小。

我们先大致看下InnoDB的undo在不同的版本上的一些演进:

MySQL 5.5的版本上
InnoDB undo是放在系统表空间即ibdata file文件中,这样如果有比较大的事务(即需要生成大量undo的),会撑大ibdata数据文件,
虽然空间可以重用, 但文件大小不能更改。
关于回滚段的,只有这个主要的参数,用来设置多少个rollback segment。

mysql> show global variables like ‘%rollback_segment%‘;
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| innodb_rollback_segments   | 128   |
+----------------------------+-------+

MySQL 5.6的版本上
InnoDB undo支持独立表空间, 增加如下参数:

+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_undo_directory   | .     |
| innodb_undo_logs        | 128   |
| innodb_undo_tablespaces | 1     |
+-------------------------+-------+

这样,在install的时候,就会在data目录下增加undo数据文件,来组成undo独立表空间,但文件变大之后的空间回收还是成为问题。

MySQL 5.7的版本上
InnoDB undo在支持独立表空间的基础上,支持表空间的truncate功能,增加了如下参数:

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的重新初始化。

接下来我们详细看下5.7的InnoDB undo的管理:

2. undo表空间创建

设置innodb_undo_tablespaces的个数, 在mysql install的时候,创建指定数量的表空间。
InnoDB支持128个undo logs,这里特别说明下,从5.7开始,innodb_rollback_segments的名字改成了innodb_undo_logs,但表示的都是回滚段的个数。
从5.7.2开始,其中32个undo logs为临时表的事务分配的,因为这部分undo不记录redo,不需要recovery,另外从33-128一共96个是redo-enabled undo。

rollback segment的分配如下:

Slot-0: reserved for system-tablespace.
Slot-1....Slot-N: reserved for temp-tablespace.
Slot-N+1....Slot-127: reserved for system/undo-tablespace. */

其中如果是临时表的事务,需要分配两个undo logs,其中一个是non-redo undo logs;这部分用于临时表数据的回滚。 另外一个是redo-enabled undo log,是为临时表的元数据准备的,需要recovery。

而且, 其中32个rollback segment创建在临时表空间中,并且临时表空间中的回滚段在每次server start的时候,需要重建。

每一个rollback segment可以分配1024个slot,也就是可以支持96*1024个并发的事务同时, 但如果是临时表的事务,需要占用两个slot。

InnoDB undo的空间管理简图如下:

注核心结构说明:

1. rseg slot
rseg slot一共128个,保存在ibdata系统表空间中,其位置在:

      /*!< the start of the array of rollback segment specification slots */
      #define	TRX_SYS_RSEGS		(8 + FSEG_HEADER_SIZE)

每一个slot保存着rollback segment header的位置。包括space_id + page_no,占用8个bytes。其宏定义:

/* Rollback segment specification slot offsets */
/*-------------------------------------------------------------*/
#define	TRX_SYS_RSEG_SPACE	0	/* space where the segment
					header is placed; starting with
					MySQL/InnoDB 5.1.7, this is
					UNIV_UNDEFINED if the slot is unused */
#define	TRX_SYS_RSEG_PAGE_NO	4	/*  page number where the segment
					header is placed; this is FIL_NULL
					if the slot is unused */

/* Size of a rollback segment specification slot */
#define TRX_SYS_RSEG_SLOT_SIZE	8

2. rseg header
rseg header在undo表空间中,每一个rseg包括1024个undo segment slot,每一个slot保存着undo
segment header的位置,包括page_no,暂用4个bytes,因为undo
segment不会跨表空间,所以space_id就没有必要了。
其宏定义如下:

/* Undo log segment slot in a rollback segment header */
/*-------------------------------------------------------------*/
#define	TRX_RSEG_SLOT_PAGE_NO	0	/* Page number of the header page of
					an undo log segment */
/*-------------------------------------------------------------*/
/* Slot size */
#define TRX_RSEG_SLOT_SIZE	4

3. undo segment header
undo segment header page即段内的第一个undo page,其中包括四个比较重要的结构:

undo segment header 进行段内空间的管理
undo page header page内空间的管理,page的类型:FIL_PAGE_UNDO_LOG
undo header 包含undo record的链表,以便安装事务的反顺序,进行回滚
undo record 剩下的就是undo记录了。

3. undo段的分配

undo段的分配比较简单,其过程如下:

首先是rollback segment的分配:

trx->rsegs.m_redo.rseg = trx_assign_rseg_low(
  srv_undo_logs, srv_undo_tablespaces,
  TRX_RSEG_TYPE_REDO);
  1. 使用round-robin的方式来分配rollback segment
  2. 如果有单独设置undo表空间,就不使用system表空间中的undo segment
  3. 如果设置的是truncate的就不分配
  4. 一旦分配了,就设置trx_ref_count,不允许truncate。

具体代码参考:

/******************************************************************//**
Get next redo rollback segment. (Segment are assigned in round-robin fashion).
@return assigned rollback segment instance */
static
trx_rseg_t*
get_next_redo_rseg(
/*===============*/
	ulong	max_undo_logs,	/*!< in: maximum number of UNDO logs to use */
	ulint	n_tablespaces)	/*!< in: number of rollback tablespaces */

其次是undo segment的创建:

从rollback segment里边选择一个free的slot,如果没有,就会报错,通常是并发的事务太多。
错误日志如下:

ib::warn() << "Cannot find a free slot for an undo log. Do"
	" you have too many active transactions running"
	" concurrently?";

如果有free,就创建一个undo的segment。

核心的代码如下:

/***************************************************************//**
Creates a new undo log segment in file.
@return DB_SUCCESS if page creation OK possible error codes are:
DB_TOO_MANY_CONCURRENT_TRXS DB_OUT_OF_FILE_SPACE */
static
dberr_t
trx_undo_seg_create(
/*================*/
	trx_rseg_t*	rseg __attribute__((unused)),/*!< in: rollback segment */
	trx_rsegf_t*	rseg_hdr,/*!< in: rollback segment header, page
				x-latched */
	ulint		type,	/*!< in: type of the segment: TRX_UNDO_INSERT or
				TRX_UNDO_UPDATE */
	ulint*		id,	/*!< out: slot index within rseg header */
	page_t**	undo_page,
				/*!< out: segment header page x-latched, NULL
				if there was an error */
	mtr_t*		mtr)	/*!< in: mtr */

	/*	fputs(type == TRX_UNDO_INSERT
	? "Creating insert undo log segment\n"
	: "Creating update undo log segment\n", stderr); */
	slot_no = trx_rsegf_undo_find_free(rseg_hdr, mtr);

	if (slot_no == ULINT_UNDEFINED) {
		ib::warn() << "Cannot find a free slot for an undo log. Do"
			" you have too many active transactions running"
			" concurrently?";

		return(DB_TOO_MANY_CONCURRENT_TRXS);
	}

4. undo的truncate

undo的truncate主要由下面两个参数控制:innodb_purge_rseg_truncate_frequency,innodb_undo_log_truncate。

  1. innodb_undo_log_truncate是开关参数。
  2. innodb_purge_rseg_truncate_frequency默认128,表示purge undo轮询128次后,进行一次undo的truncate。

当设置innodb_undo_log_truncate=ON的时候, undo表空间的文件大小,如果超过了innodb_max_undo_log_size, 就会被truncate到初始大小,但有一个前提,就是表空间中的undo不再被使用。

其主要步骤如下:

  1. 超过大小了之后,会被mark truncation,一次会选择一个
  2. 选择的undo不能再分配新给新的事务
  3. purge线程清理不再需要的rollback segment
  4. 等所有的回滚段都释放了后,truncate操作,使其成为install db时的初始状态。

默认情况下, 是purge触发128次之后,进行一次rollback segment的free操作,然后如果全部free就进行一个truncate。

但mark的操作需要几个依赖条件需要满足:

  1. 系统至少得有两个undo表空间,防止一个offline后,至少另外一个还能工作
  2. 除了ibdata里的segment,还至少有两个segment可用
  3. undo表空间的大小确实超过了设置的阈值

其核心代码参考:

/** Iterate over all the UNDO tablespaces and check if any of the UNDO
tablespace qualifies for TRUNCATE (size > threshold).
@param[in,out]	undo_trunc	undo truncate tracker */
static
void
trx_purge_mark_undo_for_truncate(
	undo::Truncate*	undo_trunc)

因为,只要你设置了truncate = on,MySQL就尽可能的帮你去truncate所有的undo表空间,所以它会循环的把undo表空间加入到mark列表中。

最后,循环所有的undo段,如果所属的表空间是marked truncate,就把这个rseg标志位不可分配,加入到trunc队列中,在purge的时候,进行free rollback segment。

注意:
如果是在线库,要注意影响,因为当一个undo tablespace在进行truncate的时候,不再承担undo的分配。只能由剩下的undo 表空间的rollback segment接受事务undo空间请求。



MySQL 5.7 新特性系列,下次进行group replication的分享,敬请期待######################################################################

继上三期月报: MySQL 5.7新特性之一介绍了一些新特性及兼容性问题 MySQL 5.7新特性之二介绍了临时表的优化和实现 MySQL 5.7新特性之三介绍了undo表空间的truncate功能

这期我们一起来学习下MySQL 5.7的并行复制。

1. 背景

MySQL的master<->slave的部署结构,使用binlog日志保持数据的同步,全局有序的binlog在备库按照提交顺序进行回放。 由于新硬件的发展,SSD的引入和多core的CPU,master节点的并发处理能力持续提升,slave节点完全按照binlog写入顺序的单线程回放,已完全跟不上master节点的吞吐能力,导致HA切换和数据保护带来巨大的挑战。

2. 并行复制的演进

从MySQL5.5版本以后,开始引入并行复制的机制,是MySQL的一个非常重要的特性。

MySQL5.6开始支持以schema为维度的并行复制,即如果binlog row event操作的是不同的schema的对象,在确定没有DDL和foreign key依赖的情况下,就可以实现并行复制。

社区也有引入以表为维度或者以记录为维度的并行复制的版本,不管是schema,table或者record,都是建立在备库slave实时解析row格式的event进行判断,保证没有冲突的情况下,进行分发来实现并行。

MySQL5.7的并行复制,multi-threaded slave即MTS,期望最大化还原主库的并行度,实现方式是在binlog event中增加必要的信息,以便slave节点根据这些信息实现并行复制。

下面我们就来看下MySQL 5.7的实现方式:

3. MySQL 5.7 并行复制

MySQL 5.7的并行复制建立在group commit的基础上,所有在主库上能够完成prepared的语句表示没有数据冲突,就可以在slave节点并行复制。
我们先来回顾一下group commit的情况:

1. group commit的过程:
    1. binlog prepare
    2. InnoDB prepare
    3. binlog commit(ordered commit)
        --3.1 Stage #1: flushing transactions to binary log
        --3.2 Stage #2: Syncing binary log file to disk
        --3.3 Stage #3: Commit all transactions in order.
    4. InnoDB commit

在ordered commit的过程中:

  1. 由leader线程帮助FLUSH队列中的线程完成flush binlog操作,
  2. 由leader线程帮助SYNC队列中的线程完成sync binlog操作,

为了表示主库并行度,在binlog row event增加了如下的标识:

#160807 15:48:10 server id 100  end_log_pos 739 CRC32 0x2237b2ef        GTID    last_committed=0        sequence_number=3
SET @@SESSION.GTID_NEXT= ‘8108dc48-47de-11e6-8690-a0d3c1f20ae4:3‘/*!*/;

即在gtid_event中增加两个字段:

class Gtid_event: public Binary_log_event
{
public:
  /*
    The transaction‘s logical timestamps used for MTS: see
    Transaction_ctx::last_committed and
    Transaction_ctx::sequence_number for details.
    Note: Transaction_ctx is in the MySQL server code.
  */
  long long int last_committed;
  long long int sequence_number;
  /**
    Ctor of Gtid_event

    The layout of the buffer is as follows
    +-------------+-------------+------------+---------+----------------+
    | commit flag | ENCODED SID | ENCODED GNO| TS_TYPE | logical ts(:s) |
    +-------------+-------------+------------+---------+----------------+
    TS_TYPE is from {G_COMMIT_TS2} singleton set of values

代码中为每一个transaction准备了如下的字段:

class Transaction_ctx
{
    ......
    int64 last_committed;
    int64 sequence_number;
}

MYSQL_BIN_LOG全局对象中维护了两个结构:

class MYSQL_BIN_LOG: public TC_LOG
{
  ......
  /* Committed transactions timestamp */
   Logical_clock max_committed_transaction;
  /* "Prepared" transactions timestamp */
   Logical_clock transaction_counter;
}

事务中的sequence_number是一个全局有序递增的数字,每个事务递增1,来源mysql_bin_log.tranaction_counter.
和gtid一对一的关系,即在flush阶段,和gtid生成的时机一致,代码参考:

binlog_cache_data::flush
{
     if (flags.finalized) {
       Transaction_ctx *trn_ctx= thd->get_transaction();
       trn_ctx->sequence_number= mysql_bin_log.transaction_counter.step();
     }
     .......
     mysql_bin_log.write_gtid(thd, this, &writer)))
     mysql_bin_log.write_cache(thd, this, &writer);
}

事务中last_committed表示在这个commit下的事务,都是可以并行的,即没有冲突, Transaction_ctx中的last_committed在每个语句prepared的时候进行初始化,来源mysql_bin_log.max_committed_transaction

static int binlog_prepare(handlerton *hton, THD *thd, bool all)
{
    ......
    Logical_clock& clock= mysql_bin_log.max_committed_transaction;
    thd->get_transaction()->
      store_commit_parent(clock.get_timestamp());
}

而mysql_bin_log.max_committed_transaction的更新是在group commit提交的时候进行变更。

MYSQL_BIN_LOG::process_commit_stage_queue(THD *thd, THD *first)
{
    ......
    for (THD *head= first ; head ; head = head->next_to_commit)
    {
        if (thd->get_transaction()->sequence_number != SEQ_UNINIT)
            update_max_committed(head);
    }
}

即获取这个group commit队列中的最大的sequence_number当成当前的max_committed_transaction。

所以,这个机制可以理解成,在group commit完成之前,所有可以成功prepared的语句,没有事实上的冲突, 分配成相同的last_committed,就可以在slave节点并行复制。

例如下面时序的事务:

session 1:insert into t1 value(100, ‘xpchild‘);
session 2:insert into t1 value(101, ‘xpchild‘);
session 2:commit
session 1:commit

Binlog日志片段如下:

# at 1398
#160807 15:38:14 server id 100  end_log_pos 1463 CRC32 0xd6141f71       GTID    last_committed=5        sequence_number=6
SET @@SESSION.GTID_NEXT= ‘8108dc48-47de-11e6-8690-a0d3c1f20ae4:6‘/*!*/;
‘/*!*/;
### INSERT INTO `tp`.`t1`
### SET
###   @1=101 /* INT meta=0 nullable=0 is_null=0 */
###   @2=‘xpchild‘ /* VARSTRING(100) meta=100 nullable=1 is_null=0 */

COMMIT/*!*/;
# at 1658
#160807 15:38:24 server id 100  end_log_pos 1723 CRC32 0xa24923a8       GTID    last_committed=5        sequence_number=7
SET @@SESSION.GTID_NEXT= ‘8108dc48-47de-11e6-8690-a0d3c1f20ae4:7‘/*!*/;
### INSERT INTO `tp`.`t1`
### SET
###   @1=100 /* INT meta=0 nullable=0 is_null=0 */
###   @2=‘xpchild‘ /* VARSTRING(100) meta=100 nullable=1 is_null=0 */

两个insert语句在prepared的时候,没有事实上的冲突,都获取当前最大的committed number = 5. 提交的过程中,保持sequence_number生成时候的全局有序性,备库恢复的时候,这两个事务就可以并行完成。

但又如下面的case:

session 1: insert into t1 value(100, ‘xpchild‘);

session 2: insert into t1 value(101, ‘xpchild‘);
session 2: commit;

session 3: insert into t1 value(102, ‘xpchild‘);
session 3: commit;

session 1: commit;

产生如下的顺序:

#160807 15:47:58 server id 100  end_log_pos 219 CRC32 0x3f295e2b        GTID    last_committed=0        sequence_number=1
### INSERT INTO `tp`.`t1`
### SET
###   @1=101 /* INT meta=0 nullable=0 is_null=0 */
.....
#160807 15:48:05 server id 100  end_log_pos 479 CRC32 0xda52bed0        GTID    last_committed=1        sequence_number=2
### INSERT INTO `tp`.`t1`
### SET
###   @1=102 /* INT meta=0 nullable=0 is_null=0 */
......
#160807 15:48:10 server id 100  end_log_pos 739 CRC32 0x2237b2ef        GTID    last_committed=0        sequence_number=3
### INSERT INTO `tp`.`t1`
### SET
###   @1=100 /* INT meta=0 nullable=0 is_null=0 */
....

session 1和session 2语句执行不冲突,分配了相同的last_committed,
session 2提交,推高了last_committed,所以session 3的laste_committed变成了1,
最后session 1提交。

注意: 这就是MySQL 5.7.3之后的改进:

在MySQL 5.7.3之前,必须在一个group commit之内的事务,才能够在slave节点并行复制,但如上面的这个case。

session 1 和session 2虽然commit的时间有差,并且不在一个group commit,生成的binlog也没有连续,但事实上是可以并行恢复执行的。

所以从MySQL 5.7.3之后,并行恢复,减少了group commit的依赖,但group commit仍然对并行恢复起着非常大的作用。

4. MySQL 5.7 并行复制参数

MySQL 5.7增加了如下参数:

mysql> show global variables like ‘%slave_parallel_type%‘;
+---------------------+---------------+
| Variable_name       | Value         |
+---------------------+---------------+
| slave_parallel_type | LOGICAL_CLOCK |
+---------------------+---------------+
1 row in set (0.00 sec)

slave_parallel_type取值:

  1. DATABASE: 默认值,兼容5.6以schema维度的并行复制
  2. LOGICAL_CLOCK: MySQL 5.7基于组提交的并行复制机制

5. MySQL 5.7 并行复制影响因素

group commit delay

首先,并行复制必须建立在主库的真实负载是并行的基础上,才能使MTS有机会在slave节点上完成并行复制, 其次,MySQL 5.7前面讨论的实现机制,可以人工的增加group commit的delay,打包更多的事务在一起,提升slave复制的并行度。但从5.7.3开始,已经减少了group commit的依赖, 尽量减少delay参数设置对主库的影响。

合理设置如下参数;

mysql> show global variables like ‘%group_commit%‘;
+-----------------------------------------+--------+
| Variable_name                           | Value  |
+-----------------------------------------+--------+
| binlog_group_commit_sync_delay          | 100000 |
| binlog_group_commit_sync_no_delay_count | 0      |
+-----------------------------------------+--------+

6. 一点建议

  1. 尽量使用row格式的binlog
  2. slave_parallel_workers 太多的线程会增加线程间同步的开销,建议4-8个slave线程,根据测试来最终确定。
  3. 如果客户端有并行度,不用刻意增加master的group commit,减少对主库的影响。

另外: booking在使用的时候遇到的如下case:

数据库的部署结构是:master->slave1->slave2

假设,当t1,t2,t3,t4四个事务在master group commit中,那么slave1线程就可以并行执行这四个事务, 但在slave1执行的过程中,分成了两个group commit,那么在slave2节点上,并行度就降低了一倍。

booking给出的后续的解法,如果slave不多,建议都挂载在master上,如果slave过多,考虑使用binlog server,来避免这样的问题。

但其实在slave1节点上进行并行恢复的时候,保持着主库的last_committed和sequence_number不变,虽然无法保证binlog写入的顺序完全和主库一致,但可以缓解这种情况。



原文地址:https://www.cnblogs.com/igoodful/p/12151405.html

时间: 2024-10-08 12:26:59

5.7新特性的相关文章

web新特性 之 WebSocket

详情参见:你真的了解WebSocket吗?     WebSocket系列教程   HTML5新特性之WebSocket WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HTML5规范中被引用为TCP连接,作为基于TCP的套接字API的占位符.它实现了浏览器与服务器全双工(full-duplex)通信.其本质是保持TCP连接,在浏览器和服务端通过Socket进行通信. 服务端与客户端的连接不断开,实现全双工的操作.及服务端或是客户端都会给对方发送消息. WebSocke

Java精品高级课,架构课,java8新特性,P2P金融项目,程序设计,功能设计,数据库设计,第三方支付,web安全,视频教程

36套精品Java架构师,高并发,高性能,高可用,分布式,集群,电商,缓存,性能调优,设计模式,项目实战,P2P金融项目,大型分布式电商实战视频教程 视频课程包含: 高级Java架构师包含:Spring boot.Spring  cloud.Dubbo.Elasticsearch,Redis.ActiveMQ.Nginx.Mycat.Spring.MongoDB.ZeroMQ.Git.Nosql.Jvm.Mecached.Netty.Nio.Mina.java8新特性,P2P金融项目,程序设计,

Atitit js es5 es6新特性 attilax总结

1.1. JavaScript发展时间轴:1 1.2. 以下是ES6排名前十的最佳特性列表(排名不分先后):1 1.3. Es6 支持情况 基本chrome ff 360se8全面支持了2 2. ECMAScript 2015(ES6)的十大特征 – WEB前端开发 - 专注前端开发,关注用户体验.html2 1.1. JavaScript发展时间轴: 1.1995:JavaScript诞生,它的初始名叫LiveScript. 2.1997:ECMAScript标准确立. 3.1999:ES3出

CSS3的新特性 行内盒子before和after

CSS3的新特性 1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>before after</title> 6 <style type="text/css"> 7 /*在DIV1盒子内部前面*/ 8 #div1:before{ 9 width: 100p

Xcode And iOS9新特性

Xcode And iOS9 1. Xcode7 新特性 > 可直接在真机上运行自己的应用,只需要有苹果账号,无需购买苹果开发者账号. > 可设置在出现 EXC_BAD_ACCESS 错误时,显示更详细的错误信息. 设置方式:XCode->Product->Scheme->Edit Scheme 左侧选择“Run” 右侧选择“Diagnostics(诊断)” 在“Runtime Sanitization(运行时净化处理)” 勾选“Enable Address Sanitize

Atitit.业务系统的新特性&#160;开发平台&#160;新特性的来源总结

Atitit.业务系统的新特性 开发平台 新特性的来源总结 1.1. 语言新特性(java c# php js python lisp c++ oc swift ruby  go dart1 1.2. 流行lib.frmawork  新特性 jdk clr framework 新特性 (jq   Servlet2 1.3. Ide 新特性( eclipse vs netbea jetbrain2 1.4. Vm  新特性 clr  jvm(jvm好像没有独立的版本号,继承在jdk里面2 1.5.

Atitit.mysql&#160;5.0&#160;5.5&#160;&#160;5.6&#160;5.7&#160;&#160;新特性&#160;新功能

Atitit.mysql 5.0 5.5  5.6 5.7  新特性 新功能 1. MySQL  5.6    5 大新特性1 1.1. 优化器的改进1 1.2. InnoDB 改进1 1.3. 使用 memcached API 直接访问 NoSQL2 1.4. 更好的复制2 1.5. Performance Schema2 2. MySQL 5.7.62 2.1. 内建中文全文索引2 2.2. 多主复制2 2.3. other2 3. 参考2 1. MySQL  5.6    5 大新特性 M

TypeScript系列1-1.5版本新特性

1. 简介 随着PC端快速向移动端迁移,移动(体验)优先的概念也越来越响.由于ReactJS目前移动端仅仅支持iOS,因此移动端Web开发框架只能选择: AngularJS/Angula2 + Ionic框架 + Cordova.想要学习好Angula2以及阅读其代码, 就必须了解和学习TypeScript,也因此需要学习好ES6以及Web Component.近期将开始学习TypeScript语言. 下面先看看TypeScript语言的发展: 鉴于JavaScript这种脚本语言很难应用于大规

jdk8新特性

jAVA8 十大新特性 投稿:junjie 字体:[增加 减小] 类型:转载 本教程将Java8的新特新逐一列出,并将使用简单的代码示例来指导你如何使用默认接口方法,lambda表达式,方法引用以及多重Annotation,之后你将会学到最新的API上的改进,比如流,函数式接口,Map以及全新的日期API "Java is still not dead-and people are starting to figure that out." 本教程将用带注释的简单代码来描述新特性,你将

atitit.Windows Server 2003 2008 2012系统的新特性 attilax 总结

atitit.Windows Server 2003  2008  2012系统的新特性 attilax 总结 1. Windows Server 2008 新特性也可以归纳为4个方面. 1 2. 相比Windows Server 08,R2 2 3. win 2012  新特性 2 4. 参考 6 1. Windows Server 2008 新特性也可以归纳为4个方面. 即将发布的Windows Server 2008是企业级的应用平台,可以为CRM等企业级应用提供更好更强的支撑,其新特性也