MySQL 8.0 新特性的一些典型应用场景

2023-08-04 12:12:38 浏览数 (1)

测试机器:本地虚拟机 测试表 sys_test,数据行 780w 测试实例1:8.0.30 测试实例2:5.7.20

一:Instant add column

Adding a column 测试:

代码语言:javascript复制
实例1:8.0.30
  mysql> alter table sys_test add column task_ename varchar(20);
  Query OK, 0 rows affected (1.24 sec)
 
实例2:5.7.20
  mysql> alter table sys_test add column task_ename varchar(20);
  Query OK, 0 rows affected (6 min 15.12 sec)
 
加字段是业务中非常频繁的需求,以上测试表明 8.0 Instant add column 的特性明显快于 5.7 版本,基本实现了秒加字段,原因是 8.0 版本 instant 算法只是修改了元数据。
 
# 关于 8.0 Instant add column 的原理请参考: http://mysql.taobao.org/monthly/2020/03/01/
# 8.0.29 之后的版本支持 Instant drop column

二:InnoDB Parallel Read

从 8.0.14 新增了一个参数 innodb_parallel_read_threads, 支持在 聚集索引上并行 check table,提升 count( ) 函数的计算速度

代码语言:javascript复制
实例1:8.0.30
  mysql> set innodb_parallel_read_threads = 8;
  mysql> select count(*) from sys_test;
   ---------- 
  | count(*) |
   ---------- 
  |  7860946 |
   ---------- 
  1 row in set (7.82 sec)
 
实例2:5.7.20
  mysql> select count(*) from sys_test;
   ---------- 
  | count(*) |
   ---------- 
  | 7860946 |
   ---------- 
  1 row in set (33.94 sec)
 
mysql> desc select count(id) from sys_test;
 ---- ------------- ---------- ------------ ------- --------------- --------- --------- ------ --------- ---------- ------------- 
| id | select_type | table    | partitions | type  | possible_keys | key     | key_len | ref  | rows    | filtered | Extra       |
 ---- ------------- ---------- ------------ ------- --------------- --------- --------- ------ --------- ---------- ------------- 
|  1 | SIMPLE      | sys_test | NULL       | index | NULL          | PRIMARY | 162     | NULL | 7764944 |   100.00 | Using index |
 ---- ------------- ---------- ------------ ------- --------------- --------- --------- ------ --------- ---------- -------------   
 
count() 聚合函数是使用比较频繁的操作,测试表明当 8.0 开启 parallel read 时,在 count() 走聚集索引的情况下,速度较 5.7 版本有 3-4 倍的提升。
 
# innodb_parallel_read_threads 只对聚集索引有效,当 count() 函数走二级索引时,测试发现 8.0 和 5.7 速度没有变化。
# parallel read 的原理请参考:https://zhuanlan.zhihu.com/p/408987542

三:InnoDB Parallel DDL

从 8.0.27 新增了一个参数 innodb_ddl_threads , 用于创建二级索引时,设置 排序和生成 BTree 的并发线程数

代码语言:javascript复制
实例1:8.0.30 
  mysql> set innodb_ddl_threads = 4 
  mysql> alter table sys_test add index TASK_ID_STATE_INDEX(task_id,state);
 Query OK, 0 rows affected (2 min 33.72 sec)
 
实例2:5.7.20 
  mysql> alter table sys_test add index TASK_ID_STATE_INDEX(task_id,state);
 Query OK, 0 rows affected (6 min 27.79 sec)
  
添加 联合索引,对比 8.0 parallel ddl 和 5.7 单线层 ddl,速度提升 3倍左右。实现环境为虚拟机 4核服务器,ssd   高配服务器,相信差距会更明显。
 
# 并行 DDL 不是新技术了,只是在 mysql 中最近一年才支持。
# 并行 DDL 的原理见:https://docs.pingcap.com/zh/tidb/stable/ddl-introduction  

四:Window Function (窗口函数)

oracle ,SQLServer 很早就支持了窗口函数,mysql 直到 8.0 版本才提供支持。熟悉数据库的同学对窗口函数应该不陌生。以下举例如何使用窗口函数:(以 rank() 专用窗口函数为例 )

代码语言:javascript复制
按 sale 销售额排名(跳跃式排名):
mysql> select *,rank() over(order by sale) from sales;
 ---------------- ------------- -------- ---------------------------- 
| sales_employee | fiscal_year | sale   | rank() over(order by sale) |
 ---------------- ------------- -------- ---------------------------- 
| Alice          |        2017 | 100.00 |                          1 |
| Bob            |        2016 | 100.00 |                          1 |
| Alice          |        2016 | 150.00 |                          3 |
| Bob            |        2017 | 150.00 |                          3 |
| John           |        2017 | 150.00 |                          3 |
| Alice          |        2018 | 200.00 |                          6 |
| Bob            |        2018 | 200.00 |                          6 |
| John           |        2016 | 200.00 |                          6 |
| John           |        2018 | 250.00 |                          9 |
 ---------------- ------------- -------- ---------------------------- 
 
按 sale 销售额排名(连续排名):
mysql> select *,dense_rank() over(order by sale) from sales;
 ---------------- ------------- -------- ---------------------------------- 
| sales_employee | fiscal_year | sale   | dense_rank() over(order by sale) |
 ---------------- ------------- -------- ---------------------------------- 
| Alice          |        2017 | 100.00 |                                1 |
| Bob            |        2016 | 100.00 |                                1 |
| Alice          |        2016 | 150.00 |                                2 |
| Bob            |        2017 | 150.00 |                                2 |
| John           |        2017 | 150.00 |                                2 |
| Alice          |        2018 | 200.00 |                                3 |
| Bob            |        2018 | 200.00 |                                3 |
| John           |        2016 | 200.00 |                                3 |
| John           |        2018 | 250.00 |                                4 |
 ---------------- ------------- -------- ---------------------------------- 

mysql 支持了很多专用窗口函数,和一些普通的聚合函数开窗,这里不一 一举例,窗口函数在涉及到 组内排序、排名的场景提供了方便,降低了原生 SQL 在这些场景中的复杂性。

mysql 窗口函数使用手册请参考:window-functions-usage

五:Functional index (函数索引)

函数索引也不是新技术,oracle 早有支持,只是 mysql 8.0 才提供支持。我们知道在 5.7 以前的版本中在 索引列上使用表达式或者函数进行查询时,会导致索引失效,这个问题 8.0 版本可以通过创建函数索引来解决:

表达式索引测试
代码语言:javascript复制
# 5.7 版本中:
mysql> show create table t3 G;
*************************** 1. row ***************************
       Table: t3
Create Table: CREATE TABLE `t3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `age_idx` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
 
 
mysql> desc select * from t3 where age   1 = 11;   # 虽然 age 列上有索引,但是当我们使用表达式  age   1 查询时,却无法使用到  age 索引
 ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------- 
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
 ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------- 
|  1 | SIMPLE      | t3    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | Using where |
 ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------- 
1 row in set, 1 warning (0.00 sec)
 
 
# 8.0 版本中: 可以创建 create index idx_age__1 on t3 ((age   1)); 表达式索引解决这个问题
mysql> create index idx_age__1 on t3 ((age   1));
Query OK, 0 rows affected (0.07 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> show create table t3 G;
*************************** 1. row ***************************
       Table: t3
Create Table: CREATE TABLE `t3` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `age_idx` (`age`),
  KEY `idx_age__1` (((`age`   1)))   # 表达式索引
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
 
mysql> desc select * from t3 where age   1 = 11;  # 此时可以利用到索引进行  age   1 查询数据
 ---- ------------- ------- ------------ ------ --------------- ------------ --------- ------- ------ ---------- ------- 
| id | select_type | table | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
 ---- ------------- ------- ------------ ------ --------------- ------------ --------- ------- ------ ---------- ------- 
|  1 | SIMPLE      | t3    | NULL       | ref  | idx_age__1    | idx_age__1 | 9       | const |    1 |   100.00 | NULL  |
 ---- ------------- ------- ------------ ------ --------------- ------------ --------- ------- ------ ---------- ------- 
函数索引测试
代码语言:javascript复制
# 5.7 版本
mysql> show create table t3 G;
*************************** 1. row ***************************
       Table: t3
Create Table: CREATE TABLE `t3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `age_idx` (`age`),
  KEY `idx_ctime` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
 
ERROR:
No query specified
 
mysql> desc select * from t3 where DATE_FORMAT(create_time,'%Y-%m-%d')='2023-07-07';  # 众所周知,在索引字段上使用函数会导致索引失效
 ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------- 
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
 ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------- 
|  1 | SIMPLE      | t3    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |   100.00 | Using where |
 ---- ------------- ------- ------------ ------ --------------- ------ --------- ------ ------ ---------- ------------- 
1 row in set, 1 warning (0.00 sec)
 
 
# 8.0 版本:可以创建函数索引
mysql> create index idx_func_create_time on t3 ((DATE_FORMAT(create_time,'%Y-%m-%d'))); # 创建函数索引
Query OK, 0 rows affected (0.05 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> show create table t3 G;
*************************** 1. row ***************************
       Table: t3
Create Table: CREATE TABLE `t3` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `age_idx` (`age`),
  KEY `idx_age__1` (((`age`   1))),
  KEY `idx_func_create_time` ((date_format(`create_time`,_utf8mb4'%Y-%m-%d')))  # 函数索引
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)
 
ERROR:
No query specified
 
mysql> desc select * from t3 where DATE_FORMAT(create_time,'%Y-%m-%d')='2023-07-07';  # 此时在索引字段上使用函数,不会导致索引失效
 ---- ------------- ------- ------------ ------ ---------------------- ---------------------- --------- ------- ------ ---------- ------- 
| id | select_type | table | partitions | type | possible_keys        | key                  | key_len | ref   | rows | filtered | Extra |
 ---- ------------- ------- ------------ ------ ---------------------- ---------------------- --------- ------- ------ ---------- ------- 
|  1 | SIMPLE      | t3    | NULL       | ref  | idx_func_create_time | idx_func_create_time | 43      | const |    1 |   100.00 | NULL  |
 ---- ------------- ------- ------------ ------ ---------------------- ---------------------- --------- ------- ------ ---------- ------- 
1 row in set, 1 warning (0.00 sec)

函数索引与普通索引的数据库结构没有任何区别,只是根据表达式成了新的排序树。

六:Descending Indexes (倒排索引)

倒排索引 在 mongodb 、oracle 中 早就支持了,只是 mysql 直到 8.0 才支持。8.0 之前的版本创建索引皆为正序,当表数据量较大 并进行 order by desc 查询时,即使在索引上排序,有时候也会出现严重的性能问题。

对于业务中的大表 在索引上倒序查询时,可以创建倒叙索引提升 SQL 排序的性能

代码语言:javascript复制
# 创建倒排索引
mysql> create index idx_desc_age on t3 (age desc); 
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0
 
mysql> show create table t3 G;
*************************** 1. row ***************************
       Table: t3
Create Table: CREATE TABLE `t3` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `age_idx` (`age`),
  KEY `idx_desc_age` (`age` DESC), # 倒排索引
  KEY `idx_age__1` (((`age`   1))), # 表达式索引
  KEY `idx_func_create_time` ((date_format(`create_time`,_utf8mb4'%Y-%m-%d'))) # 函数索引
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

实际上 mysql 8.0 支持更丰富的索引类型,但仅在使用 json 类型时可用,比如 多值索引,允许在数组类型上创建索引来加快块查询速度。

七:Persisted System Variables (配置参数持久化)

我们知道,5.7 版本之前通过 set global max_connections=10000 修改全局参数时,是没有持久化到配置文件的,当数据库发生重启那么修改的参数讲还原回去。8.0 版本中允许该操持久化到配置文件

代码语言:javascript复制
mysql> show variables like 'max_connections';
 ----------------- ------- 
| Variable_name   | Value |
 ----------------- ------- 
| max_connections | 512   |
 ----------------- ------- 
1 row in set (0.00 sec)
 
mysql> set persist max_connections=1024;  # set persist 修改全局变量,将持久化到配置文件
Query OK, 0 rows affected (0.00 sec)
 
set persist 修改配置以后,会在 datadir 目录下生成 mysqld-auto.cnf 文件,该文件用来配置参数持久化
 
[root@db-test01 data]# cat mysqld-auto.cnf
{"Version": 2, "mysql_dynamic_parse_early_variables": {"max_connections": {"Value": "1024", "Metadata": {"Host": "localhost", "User": "root", "Timestamp": 1689154725638555}}}}
 
当数据库再次启动时,会先加载 my.cnf 再加载 mysqld-auto.cnf 文件

set persist 配置参数持久化特性可以很好的解决,修改了参数又忘记修改配置文件,数据库重启导致配置还原的情况。

总结:

  8.0 除了以上典型的特性,还支持 hash join ,ddl 原子性,index skip scan 等里程碑式的特性(仅限特性的使用场景)。但这些都不属于新的技术,在 oracle 中很早就提供了支持,mysql 被 oracle 收购之后,从8.0开始吸收了 orcale 的一些经验,性能上有质的提升。

以上测试的 7 个典型特性,在实际业务中具有较多的实用性。新业务中可以考虑使用 mysql 8.0.30 版本。

0 人点赞