MySQL8.0 性能测试与新特性介绍

  • 性能对比

测试内容

  • 测试mysql5.7和mysql8.0 分别在读写、只读、只写模式((oltp_read_write,oltp_read_only,oltp_write_only))下不同并发时的性能(tps,qps)

测试环境

  • 测试使用版本分别为mysql8.0.20和mysql5.7.30
  • Sysbench测试前先重启mysql服务,并清空OS的cache(避免多次测试时命中缓存)
  • 每次进行测试都是新生成测试数据后再进行mysql8.0和mysql5.7的测试
  • 每次测试时保证mysql8.0和mysql5.7参数一致
  • sysbench 测试数据: --table-size=2000000 --tables=20  --time=600

系统环境

  • 操作系统都是CentOS Linux release 7.4.1708 (Core)
  • 主要配置参数都是:

innodb_buffer_pool_size 8GB

innodb_log_buffer_size  16M

innodb_log_file_size    256M

innodb_flush_log_at_trx_commit 1

sync_binlog 1

binlog_format   ROW

log_bin ON

transaction_isolation   REPEATABLE-READ


  • sysbench 版本:

[root@cpe-172-100-1-35 jsunicom]# sysbench --version

sysbench 1.1.0


测试数据

  1. 读写模式

MySQL读写qps mysql读写性能测试_oracle

MySQL读写qps mysql读写性能测试_sql_02

  1. 只读模式

MySQL读写qps mysql读写性能测试_oracle_03

MySQL读写qps mysql读写性能测试_sql_04

  1. 只写模式

MySQL读写qps mysql读写性能测试_mysql_05

MySQL读写qps mysql读写性能测试_数据库_06

  • 新功能介绍

 

MySQL读写qps mysql读写性能测试_sql_07

  1. 账户与安全
  • 用户创建和授权

MySQL5.7创建用户和用户授权命令可以同时执行


mysql> grant all privileges on *.* to 'yuhuashi'@'%' identified by 'yuhuashi';


MySQL8.0创建用户和用户授权的命令分开执行


##创建用户

mysql> create user 'yuhuashi'@'%' identified by 'yuhuashi';

Query OK, 0 rows affected (0.04 sec)

##用户授权

mysql> grant all privileges on *.* to 'yuhuashi'@'%';

Query OK, 0 rows affected (0.10 sec)


  • 认证插件更新


##之前认证
mysql> show variables like 'default_authentication%';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| default_authentication_plugin | mysql_native_password |
+-------------------------------+-----------------------+
##之后认证
mysql> show variables like 'default_authentication%';
+-------------------------------+-----------------------+
| Variable_name                 | Value                 |
+-------------------------------+-----------------------+
| default_authentication_plugin | caching_sha2_password |



备注:如果要修改成之前的认证插件,一般有2种方式:

  1. 修改配置文件(my.cnf):

default_authentication_plugin= mysql_native_password


  1. 修改用户密码指定认证插件(这里只针对某一个用户,其他用户不受影响):

mysql> alter user 'yuhuashi'@'%' identified with mysql_native_password by 'yuhuashi';


  • 密码管理

MySQL8.0新增了允许限制使用之前的密码


#新密码不能和前面三次的密码相同

password_history =3

#新密码不能同90天内使用过的密码相同

password_require_current = 90

#默认为off;为on 时 修改密码需要用户提供当前密码 (开启后修改密码需要验证旧密码,root 用户不需要)

password_reuse_interval=on


  1. 修改密码策略:

   方式一、修改mysql配置文件(my.cnf),全局生效,但需要重启服务器


#找到mysql的配置文件 my.cnf, 我的在/opt/mysql/my.cnf

vi /opt/mysql/my.cnf

#在最后增加一行, 保存退出

password_history=3


  

 方式二、持久化参数设置,则在重启服务后也会有效(set global 重启失效)


#使用 命令 set persist password_history=3

#这个具体的实现是增加了一个配置文件

mysql> set persist password_history=3;

#查看新增的配置文件

 [root@yuhuashi ~]# more  /opt/mysql/data/mysqld-auto.cnf

{ "Version" : 1 , "mysql_server" : { "password_require_current" : { "Value" : "ON" , "Metadata" : { "Timestamp" : 1590646007245828 , "User" : "root" , "Host" : "localh

ost" } } , "password_history" : { "Value" : "3" , "Metadata" : { "Timestamp" : 1590713952266867 , "User" : "root" , "Host" : "localhost" } } } }


   方式三、对指定用户设置


mysql> alter user 'yuhuashi1'@'%' password history 3;

#通过下面的sql,可以查询我们修改的变化

mysql> select user,host,password_reuse_history from mysql.user;

#我们尝试修改密码试试

mysql> alter user 'yuhuashi'@'%' identified by 'yuhuashi';

ERROR 3638 (HY000): Cannot use these credentials for 'yuhuashi@%' because they contradict the password history policy

#上面的这个配置项成功是因为在mysql数据库中多了一张password_history的表,在这张表中记录了修改记录。

mysql> select * from mysql.password_history;

#如果我们把这个表delete 之后,就可以正常修改了,建议不要随便动这个表


  1. password_require_current变量配置说明

只针对普通用户有效,针对root等具有修改mysql.user表权限的用户无效

举例说明:

             root账户登录


mysql> alter user 'yuhuashi'@'%' identified by 'mysql';

Query OK, 0 rows affected (0.03 sec)


   yuhuashi账户登录,修改自身密码


mysql>  alter user 'yuhuashi1'@'%' identified by 'mysql';

ERROR 3638 (HY000): Cannot use these credentials for 'yuhuashi1@%' because they contradict the password history policy

mysql> alter user 'yuhuashi1'@'%' identified by 'yuyu' replace 'mysql';

Query OK, 0 rows affected (0.02 se


            

            

  • 角色管理

MySQL8.0新增了根据角色设置用户权限,如下图所示:

MySQL读写qps mysql读写性能测试_sql_08

这个特性就相当于oracle role的管理,下面举例来说明:


#创建一个名字叫rw_role的角色

mysql> create role 'rw_role';

Query OK, 0 rows affected (0.01 sec)

#给这个角色授权,赋予增删改查的权限

mysql> grant select,insert,update,delete on yuhuashi.* to 'rw_role';

Query OK, 0 rows affected (0.08 sec)

#给用户赋予角色

mysql> create user test identified by 'test';
Query OK, 0 rows affected (0.03 sec)
mysql> show grants for 'test';
+----------------------------------+
| Grants for test@%                |
+----------------------------------+
| GRANT USAGE ON *.* TO `test`@`%` |
+----------------------------------+
1 row in set (0.00 sec)
mysql> grant 'rw_role' to 'test';
Query OK, 0 rows affected (0.01 sec)
mysql> show grants for 'test';
+-----------------------------------+
| Grants for test@%                 |
+-----------------------------------+
| GRANT USAGE ON *.* TO `test`@`%`  |
| GRANT `rw_role`@`%` TO `test`@`%` |
+-----------------------------------+
2 rows in set (0.00 sec)
mysql> show grants for 'test' using 'rw_role';
+--------------------------------------------------------------------+
| Grants for test@%                                                  |
+--------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `test`@`%`                                   |
| GRANT SELECT, INSERT, UPDATE, DELETE ON `yuhuashi`.* TO `test`@`%` |
| GRANT `rw_role`@`%` TO `test`@`%`                                  |
+--------------------------------------------------------------------+
#测试,用test账户登录
mysql> select current_role();
+----------------+
| current_role() |
+----------------+
| NONE           |
+----------------+
#用set 激活
mysql> set role 'rw_role';
Query OK, 0 rows affected (0.00 sec)
mysql> select * from yuhuashi.test;
Empty set (0.00 sec)
#为用户设置默认的角色
mysql> set default role 'rw_role' to 'test';
Query OK, 0 rows affected (0.04 sec)
mysql> select * from mysql.default_roles;
+------+------+-------------------+-------------------+
| HOST | USER | DEFAULT_ROLE_HOST | DEFAULT_ROLE_USER |
+------+------+-------------------+-------------------+
| %    | test | %                 | rw_role           |
+------+------+-----------


  1. 优化器索引
  • 隐藏索引(invisible index)

特点:不会被优化器使用,但仍然需要维护

应用场景:软删除,灰度发布,新索引替换老索引

举例说明:

MySQL读写qps mysql读写性能测试_oracle_09

注意: 主键不能设置隐藏索引

  • 降序索引

MySQL8.0之前:虽然可指定降序索引,实际上是升序索引

MySQL8.0:正式意义上支持降序索引,只有InnoDB引擎支持降序并且只支持BTREE降序索引,MySQL8.0不在对group by操作进行隐式排序,需要使用order by进行排序。

MySQL读写qps mysql读写性能测试_MySQL读写qps_10

虽然age列指定了desc,但在实际的建表语句中还是将其忽略了。再来看看MySQL 8.0的结果。

MySQL读写qps mysql读写性能测试_sql_11

降序索引的意义

如果一个查询,需要对多个列进行排序,且顺序要求不一致。在这种场景下,要想避免数据库额外的排序-“filesort”(Using filesort代表查询中有排序操作),只能使用降序索引。

MySQL5.7

MySQL读写qps mysql读写性能测试_sql_12

MySQL8.0

MySQL读写qps mysql读写性能测试_sql_13

对比结果: MySQL 8.0因为降序索引的存在,避免了“filesort”。

这其实是降序索引的主要应用场景。如果只对单个列进行排序,降序索引的意义不是太大,无论是升序还是降序,升序索引完全可以应付。还是同样的表,看看下面的查询。

MySQL5.7

MySQL读写qps mysql读写性能测试_数据库_14

MySQL8.0

MySQL读写qps mysql读写性能测试_数据库_15

对比说明: 虽然c1是升序索引,但在第二个查询中,对其进行降序排列时,并没有进行额外的排序,使用的还是索引, 而在8.0中,对于反向扫描,有一个专门的词进行描述“Backward index scan”。

终于不再对group by进行隐式排序

由于降序索引的引入,MySQL 8.0再也不会对group by操作进行隐式排序。

MySQL读写qps mysql读写性能测试_oracle_16

MySQL读写qps mysql读写性能测试_sql_17

对比总结:不仅结果没有升序输出,执行计划中也没有“Using filesort”.可见,MySQL 8.0对于group by操作确实不再进行隐式排序。

  • 函数索引

mysql8.0.13开始支持在索引中使用函数(表达式)的值,之前是使用列值,现在可以使用函数表达式的值使用索引,同时也支持降序索引,json数据的索引。之前版本的数据库是没法对json里各个节点的数据索引,函数索引是基于虚拟计算列功能来实现的。可以方便对json格式数据的查询。

MySQL读写qps mysql读写性能测试_sql_18

查看执行计划:

MySQL读写qps mysql读写性能测试_MySQL读写qps_19

总结:虽然nickname有建索引,但还是全表扫描

针对JSON节点的索引

MySQL读写qps mysql读写性能测试_sql_20

  1. 通用表达式

即with子句,是sql语句的增强,很多主流数据库都有该功能,mysql8.0也是拥有了该功能。这在实际使用经常会用使用到。

  • 非递归CTE

举例说明:

               派生表(子查询):select * from (select 1) as dt;

通用表表达式:with dt  as (select 1)select * from dt;就相当于一个变量,在后面语句中使用。:

MySQL读写qps mysql读写性能测试_sql_21

另外CTE可能在SELECT/UPDATE/DELETE之前,包括with derived as ( subquery )的子查询, 例如:


with derived as (

     subquery

)

delete from table_name

where table_name.col_name in (

     select col_name from derived

);


  • 递归CTE

在查询中引用自己的定义,使用RECURSIVE表示。和编程语言中的递归函数调用差不多。生成一些模拟数据也比较方便。

举例说明:

MySQL读写qps mysql读写性能测试_sql_22

  • 递归限制

递归查询必须指定终止条件

MySQL8.0提供两个参数避免用户未指定终止条件

  1. cte_max_recursion_depth:默认值1000
mysql> show variables like 'cte_max%';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| cte_max_recursion_depth | 1000  |
+-------------------------+-------+


测试-死循环:

MySQL读写qps mysql读写性能测试_数据库_23

当前会话/持久化设置cte_max_recursion_depth


mysql> set session cte_max_recursion_depth=2000;
Query OK, 0 rows affected (0.00 sec)
mysql> set persist cte_max_recursion_depth=2000;
Query OK, 0 rows affected (0.02 sec)


  1. max_execution_time:默认无限制,单位毫秒
mysql> show variables like 'max_execution_time';
+--------------------+-------+
| Variable_name      | Value |
+--------------------+-------+
| max_execution_time | 0     |


            同样设置这个参数和cte_max_recursion_depth一样。

  • 总结
  1. 通用表达式与派生表类似,就像语句级别的临时表或试图
  2. CTE可以在查询中多次引用,可以引用其他CTE,可以递归
  3. CTE支持SELECT/INSERT/UPDATE/DELETE等语句
  4. 窗口函数
  • 窗口函数定义

MySQL8.0支持窗口函数(Windows Function),在Oracle中也成为分析函数。

窗口函数与分组聚合函数类似,但是每一行数据都生成一个结果。简单的说,聚合函数是将多条记录聚合为一条,而窗口函数是每条记录都会执行,有几条记录执行完还是几条。

聚合窗口函数:SUM/AVG/COUNT/MAX/MIN等等

  • 专用窗口函数

MySQL8.0中定义的窗口函数主要有以下几种:

函数名

参数

描述

cume_dist()

累计分布值。即分组值小于等于当前值的行数与分组总行数的比值。取值范围为(0,1]。

dense_rank()

不间断的组内排序。使用这个函数时,可以出现1,1,2,2这种形式的分组。

first_value()

是;first_value(expr)

返回分组内截止当前行的第一个值。

lag()

是;lag(expr,[N,[default]])

从当前行开始往前取第N行,如果N缺失默认为1。若没有没有,则默认返回default。default默认值为NULL

last_value()

是;last_value(expr)

返回分组内截止当前行的最后一个值。

lead()

是;lead(expr,[N,[default]])

从当前行开始往后取第N行。函数功能与lag()相反,其余与lag()相同。

nth_value()

是;nth_value(expr,N)

返回分组内截止当前行的第N行。first_value\last_value\nth_value函数功能相似,只是返回分组内截止当前行的不同行号的数据。

ntile()

是;ntile(N)

返回当前行在分组内的分桶号。在计算时要先将改分组内的所有数据划分成N个桶,之后返回每个记录所在的分桶号。返回范围从1到N

percent_rank()

累计百分比。该函数的计算结果为:小于该条记录值的所有记录的行数/该分组的总行数-1. 所以改记录的返回值为[0,1]

rank()

间断的组内排序。其排序结果可能出现如下结果:1,1,3,4,4,6

row_number()

当前行在其分组内的序号。不管其排序结果中是否出现重复值,其排序结果都为:1,2,3,4,5

注:‘参数’列说明该函数是否可以加参数。“否”说明该函数的括号内不可以加参数。expr即可以代表字段,也可以代表在字段上的计算,比如sum(col)等。

  • 举例说明:

首先创建测试用例

MySQL读写qps mysql读写性能测试_sql_24

  1. row_number(partition by xxxx order by xxx)

查询求每个用户的最新的一个订单。

MySQL读写qps mysql读写性能测试_MySQL读写qps_25

  1. rank()

类似于 row_number(),也是排序功能,但是rank()有什么不一样?假如再往测试表中写入一条数据: insert into test4 values (11,'小张',800,'2020-1-22'); 那么这时候对于测试表中的’小张’用户来说,有两条create_date完全一样的数据那么在row_number()编号的时候,这两条数据却被编了两个不同的号理论上讲,这两条的数据的排名是并列最新的。因此rank()就是为了解决这个问题的,也即:排序条件一样的情况下,其编号也一样。

MySQL读写qps mysql读写性能测试_sql_26

  1. dense_rank()

dense_rank()的出现是为了解决rank()编号存在的问题的,rank()编号的时候存在跳号的问题,如果有两个并列第1,那么下一个名次的编号就是3,结果就是没有编号为2的数据。如果不想跳号,可以使用dense_rank()替代。

MySQL读写qps mysql读写性能测试_sql_27

  1. avg,sum等聚合函数在窗口函数中的的增强

可以在聚合函数中使用窗口功能,比如sum(amount)over(partition by user_no order by create_date) as sum_amont,达到一个累积计算sum的功能。

MySQL读写qps mysql读写性能测试_sql_28

  1. NTILE (N) 将数据按照某些排序分成N组

举个简单的例子,按照分数线的倒序排列,将学生成绩分成上中下3组,可以得到哪个程序数据上中下三个组中哪一部分,就可以使用NTILE(3) 来实现。

MySQL读写qps mysql读写性能测试_mysql_29

  1. first_value(column_name) and last_value(column_name)

first_value和last_value基本上见名知意了,就是取某一组数据,按照某种方式排序的,最早的和最新的某一个字段的值。

MySQL读写qps mysql读写性能测试_MySQL读写qps_30

  1. nth_value(column_name,n)

从排序的第n行还是返回nth_value字段中的值,这个函数用的不多,要表达的这种逻辑,看个例子体会以下:

MySQL读写qps mysql读写性能测试_mysql_31

  1. cume_dist

在某种排序条件下,小于等于当前行值的行数/总行数,得到的是数据在某一个纬度的分布百分比情况。

举例说明:

第一行的数据的日期(create_date) 是2020-01-05 00:00:00,小于等于2020-01-05 00:00:00的数据是1行,计算方式是:1/5 = 0.2

第一行的数据的日期(create_date) 是2020-01-06 00:00:00,小于等于2020-01-06 00:00:00的数据是2行,计算方式是:1/5 = 0.4

以此类推,最终结果如下:

MySQL读写qps mysql读写性能测试_MySQL读写qps_32

  1. percent_rank()

同样是数据分布的计算方式,只不过算法变成了:当前RANK值-1/总行数-1 。

实际中的用的也不多,举例说明:

MySQL读写qps mysql读写性能测试_sql_33

  1. lag以及lead

lag(column,n)获取当前数据行按照某种排序规则的上n行数据的某个字段,lead(column,n)获取当前数据行按照某种排序规则的下n行数据的某个字段。

举例说明,按照时间排序,获取当前订单的上一笔订单发生时间和下一笔订单发生时间(可以计算订单的时间上的间隔度或者说买的频繁程度)

MySQL读写qps mysql读写性能测试_mysql_34

  1. InnodDB增强
  • 集成数据字典

最新的MySQL 8.0 发布之后,对数据库数据字典方面做了较大的改进。

  1. 首先是,将所有原先存放于数据字典文件中的信息,全部存放到数据库系统表中,即将之前版本的.frm,.opt,.par,.TRN,.TRG,.isl文件都移除了,不再通过文件的方式存储数据字典信息。
  2. 其次是对INFORMATION_SCHEM,mysql,sys系统库中的存储引擎做了改进,原先使用MyISAM存储引擎的数据字典表都改为使用InnoDB存储引擎存放。从不支持事务的MyISAM存储引擎转变到支持事务的InnoDB存储引擎,为原子DDL的实现,提供了可能性。
  • 原子DDL操作

MySQL8.0开始支持原子DDL操作,一个原子DDL操作,具体的操作内容包括:数据字典更新,存储引擎层的操作,在binlog中记录DDL操作。并且这些操作都是原子性的,表示中间过程出现错误的时候,是可以完整回退的。

举例说明:

MySQL5.7中执行drop 命令drop table t1,t2;如果t1存在,t2不存在,会提示t2表不存在,但是t1表仍然会被删除。

MySQL读写qps mysql读写性能测试_sql_35

MySQL8.0执行同样的drop命令,也会提示t2表不存在,但是t1表不会被删除,保证了原子性。

MySQL读写qps mysql读写性能测试_oracle_36

  • 自增列持久化
  1. MySQL8.0以前的版本InnoDB自增列计算(auto_increment)的值只存储在内存中,这样每次MySQL服务器重启后,会重新扫描表的主键最大值,如果之前已经删除过id=100的数据,但是表中当前记录的最大值如果是99,那么经过扫描,下一条记录的id是100,而不是101。
  2. MySQL8.0则是每次在变化的时候,都会将自增计数器的最大值写入redo log,同时在每次检查点将其写入引擎私有的系统表。则不会出现自增主键重复的问题。

举例说明:

MySQL读写qps mysql读写性能测试_mysql_37

重启MySQL服务

MySQL读写qps mysql读写性能测试_oracle_38

MySQL读写qps mysql读写性能测试_oracle_39

MySQL8.0

MySQL读写qps mysql读写性能测试_mysql_40

重启服务

MySQL读写qps mysql读写性能测试_oracle_41

插入数据

MySQL读写qps mysql读写性能测试_oracle_42

  • 死锁检查控制

MySQL8.0(MySQL5.7.15)增加了一个新的动态变量(innodb_deadlock_detect),用于控制系统是否执行InnoDB死锁检查。

对于高并发的系统,禁用死锁检查可带来性能的提高。因为在高并发系统中,当大量线程等待同一个锁时,死锁检查会大大拖慢数据库

  • 锁定语句选项

SELECT …FOR SHARE 和SELECT …FOR UPDATE中支持NOWAIT

SKIP LOCKED选项

对于NOWAIT ,如果请求的行被其他事务锁定时,若想获取被锁住的数据,则立即返回不可访问异常。

对于SKIP LOCKED,从返回的结果集中移除被锁定的行。

还是举例说明:

MySQL读写qps mysql读写性能测试_mysql_43

再重新打开新的窗口

MySQL读写qps mysql读写性能测试_mysql_44

  • 其他功能改进
  1. 支持部分快速DDL,ALTER TABLE …ALGORITHM=INSTANT;
  2. InnoDB临时表使用共享的临时表空间ibtmp1.
  3. 新增静态变量innodb_dedicated_server,自动配置InnoDB内存参数:

Innodb_buffer_pool_size/innodb_log_file_size等。

  1. 支持ALTER TABLESPACE …RENAME TO重命名通用表空间。
  2. 支持使用innodb_directories选项再服务器停止时将表空间文件移动到新的位置
  3. InnoDB表空间加密特性支持重做日志和撤销日志。
  4. JSON增强
  • 内联路径操作符

MySQL8.0增加了JSON操作符column->>path,等价于

JSON_UNQUOTE(column->path)或JSON_UNQUTOE(json_extract(column,path))

MySQL读写qps mysql读写性能测试_sql_45

mysql8.0还扩展了这个路径表达式的语法,可以支持范围的操作。

MySQL读写qps mysql读写性能测试_mysql_46

  • JSON聚合函数

可以将表中列的数据聚合成对应的json数组或者json对象。

MySQLl8.0(MySQL5.7.22)增加了两个聚合函数

  1. JSON_ARRAYAGG() ,用于将多行数据组合生成JSON数组。

MySQL读写qps mysql读写性能测试_sql_47

  1. JSON_OBJECTAGG(),用于生成json对象,支持多个列

MySQL读写qps mysql读写性能测试_sql_48

备注:如果存在重复值,最后面的值覆盖前面的值

  • JSON实用函数

用于对json对象的输出或者获取json所占用的存储空间。

MySQL8.0(MySQL5.7.22)增加了3个实用函数

  1. JSON_PRETTY()

这个函数用于在输出json对象内容的时候进行格式化或者美化输出。

MySQL读写qps mysql读写性能测试_sql_49

  1. JSON_STORAGE_SIZE()

返回json数据所占用的空间大小

MySQL读写qps mysql读写性能测试_数据库_50

  1. JSON_STORAGE_FREE(),MySQL8.0新增

用于更新某些json列之后,相应的一些字段它可能释放的存储空间

MySQL读写qps mysql读写性能测试_sql_51

  • JSON合并函数

MySQL8.0(MySQL5.7.22)增加了JSON_MERGE_PATCH()

MySQL8.0(MySQL5.7.22)增加了JSON_MERGE_PRESERV()

两个函数都用于合并json数据,区别在于前者重复属性会使用最新的属性值,后者会保留所有的属性值。并且废弃了json_merge()函数。

  1. JSON_MERGE_PATCH()

会覆盖旧值

MySQL读写qps mysql读写性能测试_sql_52

  1. JSON_MERGE_PRESERV()

会保留所有值

MySQL读写qps mysql读写性能测试_MySQL读写qps_53

  1. JSON_MERGE()

MySQL读写qps mysql读写性能测试_MySQL读写qps_54

  • JSON表函数

MySQL8.0增加了JSON_TABLE(),将JSON数据转换为关系表。

可以将该函数的返回结果当作一个普通的表,使用SQL进行查询。

MySQL读写qps mysql读写性能测试_mysql_55