mysql8与mysql5的单链接性能比较

1、概述

先说结论,若是你的MySQL数据库运行在一个高并发的环境下,那么MySQL8优点很大,升级到MySQL8是一个很好的选择;但若是你的MySQL运行环境是低并发的,那么MySQL8优点并不明显,我的建议不要如今就升级到MySQL8,能够等一等。java

本文针对的是低并发环境下的MySQL8与MySQL5的性能比较。mysql

1.1 背景

根据网上一些使用sysbench作的MySQL8的性能基准测试的结果来看,MySQL8相对MySQL5的性能优点更多体如今高并发环境(如链接数达到1024甚至2048)下,单位时间处理数量(例如InnoDB处理行数或处理事务数量)的极大提升。即,高并发下的TPS指标,MySQL8相对MySQL5有很大的优点。git

能够参考这篇文章 : MySQL Performance Benchmarking: MySQL 5.7 vs MySQL 8.0

但实际的生产环境上,也有不少系统并未运行在高并发环境下,它们的数据库链接数每每不会超过默认的最大链接数151,它们甚至不须要独立的MySQL服务器。对于这种场景,生产环境上是否有必要将MySQL升级到8呢?github

本文针对MySQL5.7.28MySQL8.0.22的docker镜像版本,在各自都没有作性能优化配置的基础上,在相同的宿主机环境下,在相同的表结构与相同的数据量下,对它们进行了一些彻底相同的,单个链接上的性能测试,并对其进行数据统计与分析。sql

即,本文考虑的不是高并发环境下的性能表现,而是低并发环境下,单个链接上的性能表现。此时主要关注各类SQL操做的耗时和资源消耗。docker

1.2 单链接的性能比较结论

对单个链接的性能测试结果进行统计分析以后,得出如下结论:数据库

  • 因为MySQL8对hash join的支持,对于链接字段上没有任何索引的多表链接查询,MySQL8具备压倒性的性能优点。
  • 可使用倒序索引的话,MySQL8具备必定性能优点。
  • 在其余场景的性能表现上,如单表读写,多表索引链接查询等等,MySQL8基本与MySQL5没有太大区别,甚至略有不如。
  • MySQL8对资源的消耗,如CPU和内存,要比MySQL5多一些。

1.3 低并发环境下是否升级到MySQL8的建议

对于低并发环境来讲,MySQL8对性能的最大提高来自于哈希链接的支持。但实际上由于索引的存在,实际能用到哈希链接的场景并非那么多。尤为是已经稳定运行了一段时间的生产环境上,若是链接字段上彻底没有索引且数据量较大的话,性能问题应该早就暴露出来了;并且MySQL8的版本还在不停迭代升级中,一些功能的兼容性还不是很稳定(有些功能在8.0.x较早的版本里支持,后续更高一点版本又不支持了)。安全

所以对于低并发的生产环境,我的建议:性能优化

  1. 若是没有足够的MySQL运维能力,那么不建议为了性能提高而升级MySQL到8.0.x的版本,除非肯定生产上有不少无索引的字段做为链接条件(实际上不可能)。但若是要从其余方面(安全性,NOSQL之类)考虑,好比须要使用JSON加强功能,那么能够考虑升级。
  2. 若是有足够的MySQL运维能力,能够考虑升级到MySQL8,可是运维须要提供小版本甚至主版本升级的方案与能力,并能持续对MySQL配置进行优化。

简而言之一句话,生产上先等等,等到8.1版本之后再看看服务器

至于开发或者测试环境,能够尝试一下,作一些技术准备。

1.3 主要性能数据对比

本文的性能比较主要看各类操做的耗时(或者说响应时间),以及在操做执行期间的资源(CPU与内存)消耗。

如下耗时统计与资源消耗统计均基于本地测试环境与测试数据,不能表明广泛的性能表现。只能用于相同环境与数据下Mysql5与8的性能比较。

1.3.1 耗时比较

对MySQL8与MySQL5分别进行了如下操做:

操做 操做说明 mysql8耗时 mysql5耗时
JDBC链接 - 3毫秒 2毫秒
大表写入 100万条记录分批插入,每批1000条 30秒+ 20秒+
大表扫描 单表100万记录,无条件的全表扫描 1秒+ 1秒+
索引查询 单表100万记录,普通Btree索引,等值条件查询,命中率1% 0.02~0.05秒 0.02~0.05秒
索引链接 百万记录表与十万记录表链接,链接字段是惟一索引 33秒+ 28秒+
无索引链接1 百万记录表与一万记录表链接,链接字段无索引 2秒+ 半小时左右
无索引链接2 百万记录表与100记录表链接,链接字段无索引 1.5秒+ 17秒+
独立子查询 100记录表做为百万记录表的IN条件子查询 0.8秒+ 14秒+
关联子查询 100记录表做为百万记录表的EXISTS条件子查询 0.8秒+ 18秒+
倒序排序 百万记录表创建正序倒序混合索引,并使用它排序 0.4秒+ 1.3秒+

注意:

  1. 各个测试的详细说明参考后续章节。
  2. 无索引链接1无索引链接2独立子查询以及关联子查询中,mysql8优点明显的缘由都是哈希链接的支持。就是说,这几个测试案例中的表的链接字段都是没有索引的字段。
  3. 关联子查询在MySQL8中还多一个半链接优化,但优点不明显。
  4. 500万以上的单表就应该考虑分区或分表了,这里不考虑这种场景。
  5. 关于索引链接与哈希链接的性能对比,不能一律而论谁性能表现更好,而是取决于具体的表结构与数据量。这个点与本文其实无关,但后续章节也有讨论。

1.3.2 资源消耗统计

在测试过程当中,对CPU与内存消耗进行了简单的统计,结果以下:

项目 mysql8 mysql5
批量写入百万数据过程当中的CPU使用率(%) 90 70
各类查询过程当中的CPU使用率(%) 100 100
mysql容器重启以后内存使用量(M) 341.2 205.9
各类操做以后mysql容器内存使用涨幅(M) 130 110

由此能够得出的初步结论:

  1. MySQL8的内存使用量高于MySQL5。
  2. 写入数据时,MySQL8须要更多的CPU资源。

简而言之,MySQL8比MySQL5更消耗CPU与内存资源。

2、性能测试环境

本次测试使用docker镜像,在本地启动了两个mysql容器,均没有资源限制,也没有特殊的性能优化配置。

  • MySQL5版本 : 5.7.28
  • MySQL8版本 : 8.0.22
  • 安装文件 : mysql官方提供的docker镜像
  • Docker宿主机OS : Linux Mint 19.1 Tessa
  • Docker宿主机CPU : Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz 4 core 8 process
  • Docker宿主机内存 : 32G
  • Docker宿主机磁盘 : SSD
  • MySQL5配置 :
[client]
default-character-set=utf8mb4

[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
transaction_isolation = READ-COMMITTED

[mysql]
default-character-set=utf8mb4
  • MySQL8配置 :
[mysqld]
pid-file        = /var/run/mysqld/mysqld.pid
socket          = /var/run/mysqld/mysqld.sock
datadir         = /var/lib/mysql
secure-file-priv= NULL

default_authentication_plugin = mysql_native_password
transaction_isolation = READ-COMMITTED
  • Docker容器资源限制 : 无限制

3、测试数据

3.1 DDL

分别在MySQL5与MySQL8的实例中建立以下数据库与表:

CREATE DATABASE `db_mysql_test1` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ;

USE `db_mysql_test1`;

DROP TABLE IF EXISTS `db_mysql_test1`.`tb_order`;
CREATE TABLE IF NOT EXISTS `db_mysql_test1`.`tb_order` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `ord_number` varchar(20) NOT NULL COMMENT '订单编号',
  `custom_number` varchar(20) NOT NULL COMMENT '客户编号',
  `product_number` varchar(20) NOT NULL COMMENT '商品编号',
  `warehouse_number` varchar(20) NOT NULL COMMENT '仓库编号',
  `ord_status` tinyint NOT NULL COMMENT '订单状态',
  `order_time` datetime NOT NULL COMMENT '下单时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `tb_order_unique01` (`ord_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '订单表';

DROP TABLE IF EXISTS `db_mysql_test1`.`tb_custom`;
CREATE TABLE IF NOT EXISTS `db_mysql_test1`.`tb_custom` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `custom_number` varchar(20) NOT NULL COMMENT '客户编号',
  `custom_name` varchar(50) NOT NULL COMMENT '客户姓名',
  `custom_phone` varchar(20) NOT NULL COMMENT '客户手机号',
  `custom_address` varchar(200) NOT NULL COMMENT '客户地址',
  PRIMARY KEY (`id`),
  UNIQUE KEY `tb_custom_unique01` (`custom_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '客户表';

DROP TABLE IF EXISTS `db_mysql_test1`.`tb_product`;
CREATE TABLE IF NOT EXISTS `db_mysql_test1`.`tb_product` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `product_number` varchar(20) NOT NULL COMMENT '商品编号',
  `product_name` varchar(50) NOT NULL COMMENT '商品名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '商品表';

DROP TABLE IF EXISTS `db_mysql_test1`.`tb_warehouse`;
CREATE TABLE IF NOT EXISTS `db_mysql_test1`.`tb_warehouse` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `warehouse_number` varchar(20) NOT NULL COMMENT '仓库编号',
  `warehouse_name` varchar(50) NOT NULL COMMENT '仓库名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT = '仓库表';

3.2 测试数据

本文开发了一个简单的java应用程序用于导入数据,并执行部分查询操做。

由于大部分应用开发会用到JDBC驱动,而MySQL8相对于MySQL5也提供了一个全新的驱动包。所以咱们也须要考虑不一样版本驱动包的影响。

测试程序代码能够从github或gitee自行拉取:

https://github.com/zhaochunin...

https://gitee.com/XiaTangShao...

运行mysql-test的注意事项:

  1. 使用openJDK11。
  2. 测试mysql8或mysql5时,将pom中的mysql-connector-java依赖版本修改成对应版本,而后将MySQLPerformanceTest中的JDBC_URLJDBC_USERJDBC_PWD设置为对应的值。注意不要忘记给JDBC_URL的参数添加rewriteBatchedStatements=true,缺乏此参数的话,PreparedStatementexecuteBatch将没法生效。
  3. 该程序每次生成新的随机数据并将其导入数据库。(导入前会自动执行truncate截断相关表。)
  4. 该程序除导入数据以外,还执行了四张表的全表查询,以及三个内联查询。
  5. 该程序统计了JDBC链接,批量插入,全表查询和内联查询的消耗时间。(具体结果见后续章节)

4、性能测试

分别对MySQL8MySQL5进行了如下性能测试,并统计结果以下:

4.1 JDBC链接

根据mysql-test程序测试结果,MySQL8MySQL5的JDBC链接时间基本相同。

  1. MySQL8的JDBC驱动包版本为8.0.22,对应的Driver Class是com.mysql.cj.jdbc.Driver
  2. MySQL5的JDBC驱动包版本为5.1.47,对应的Driver Class是com.mysql.jdbc.Driver
某些资料上说MySQL8若是用5.X的JDBC驱动会有性能问题。这里没有测试这种案例,正常来讲,应用程序也应该会升级JDBC驱动,不然会出警告。

4.2 大表写入

参考 mysql-test程序的insertOrder方法,向tb_order表分批插入了100万条数据,每1000条插入一次。

  • mysql8的平均耗时 : 33389毫秒,CPU使用率在90%上下。
  • mysql5的平均耗时 : 23446毫秒,CPU使用率在70%上下。

能够看到,mysql8在写入数据时,会消耗更多的CPU,耗时也更多一点。

MySQL8可能须要性能相关配置上作一些优化。

4.3 大表扫描

参考 mysql-test程序的selectOrders方法,对一张100万数据的表tb_order作了一次无条件查询,MySQL作了全表扫描,mysql8与mysql5的执行计划是同样的。

  • mysql8的平均耗时 : 1182毫秒,CPU使用率在100%上下。
  • mysql5的平均耗时 : 1311毫秒,CPU使用率在100%上下。

能够看到,二者耗时和CPU消耗基本相同,MySQL8在耗时上略占优点。

4.4 索引查询

为表tb_order建立一个普通索引,并在一个等值查询中使用它。

CREATE INDEX `tb_order_idx02` ON `db_mysql_test1`.`tb_order` (`warehouse_number`, `product_number`);

mysql5中执行:

-- 耗时大约0.04秒
SELECT * FROM tb_order where warehouse_number = 'whs_0000000075';

mysql> explain SELECT * FROM tb_order where warehouse_number = 'whs_0000000075';
+----+-------------+----------+------------+------+----------------+----------------+---------+-------+-------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys  | key            | key_len | ref   | rows  | filtered | Extra |
+----+-------------+----------+------------+------+----------------+----------------+---------+-------+-------+----------+-------+
|  1 | SIMPLE      | tb_order | NULL       | ref  | tb_order_idx02 | tb_order_idx02 | 82      | const | 19526 |   100.00 | NULL  |
+----+-------------+----------+------------+------+----------------+----------------+---------+-------+-------+----------+-------+
1 row in set, 1 warning (0.01 sec)

mysql8中执行:

-- 耗时大约0.05秒
SELECT * FROM tb_order where warehouse_number = 'whs_0000000075';

mysql> explain SELECT * FROM tb_order where warehouse_number = 'whs_0000000075';
+----+-------------+----------+------------+------+----------------+----------------+---------+-------+-------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys  | key            | key_len | ref   | rows  | filtered | Extra |
+----+-------------+----------+------------+------+----------------+----------------+---------+-------+-------+----------+-------+
|  1 | SIMPLE      | tb_order | NULL       | ref  | tb_order_idx02 | tb_order_idx02 | 82      | const | 19526 |   100.00 | NULL  |
+----+-------------+----------+------------+------+----------------+----------------+---------+-------+-------+----------+-------+
1 row in set, 1 warning (0.00 sec)

可见,对于普通索引查询来讲,mysql5与mysql8性能表现基本一致。

4.5 索引链接

参考 mysql-test程序的selectOrderJoinCustom方法,对一张100万数据的表和一张10万数据的表作链接查询,链接字段上创建了惟一索引。此时,不管MySQL8仍是MySQL5,其优化器都会选择索引链接策略。

  • mysql8的平均耗时 : 3335毫秒,CPU使用率在100%上下。
  • mysql5的平均耗时 : 2860毫秒,CPU使用率在100%上下。

能够看到,二者CPU消耗基本相同,但MySQL8在耗时上略多于MySQL5。

执行计划一致,表结构与数据量也一致,MySQL8却慢一点,仍是须要在性能相关配置上作一些优化。

查看二者的执行计划可知,二者都采用了索引链接:将tb_order做为主表,遍历其结果集的每条记录,再使用链接字段上的惟一索引tb_custom_unique01从表tb_custom中查找对应记录。即,Nested loop + eq_ref

mysql8的执行计划:

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.custom_number, b.custom_name FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys      | key                | key_len | ref                            | rows   | filtered | Extra |
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------+
|  1 | SIMPLE      | a     | NULL       | ALL    | NULL               | NULL               | NULL    | NULL                           | 994365 |   100.00 | NULL  |
|  1 | SIMPLE      | b     | NULL       | eq_ref | tb_custom_unique01 | tb_custom_unique01 | 82      | db_mysql_test1.a.custom_number |      1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

mysql> 
mysql> explain format=tree SELECT a.ord_number, a.ord_status, a.order_time, b.custom_number, b.custom_name FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                        |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Nested loop inner join  (cost=616793.16 rows=994365)
    -> Table scan on a  (cost=100902.25 rows=994365)
    -> Single-row index lookup on b using tb_custom_unique01 (custom_number=a.custom_number)  (cost=0.42 rows=1)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
explain format=tree [SQL语句] 是MySQL8.0.21版本开始新增的语法,能够查看到一些额外的详细的执行计划信息。

mysql5的执行计划:

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.custom_number, b.custom_name FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys      | key                | key_len | ref                            | rows   | filtered | Extra |
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------+
|  1 | SIMPLE      | a     | NULL       | ALL    | NULL               | NULL               | NULL    | NULL                           | 994365 |   100.00 | NULL  |
|  1 | SIMPLE      | b     | NULL       | eq_ref | tb_custom_unique01 | tb_custom_unique01 | 82      | db_mysql_test1.a.custom_number |      1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

4.6 无索引链接

参考 mysql-test程序的selectOrderJoinProduct方法与selectOrderJoinWarehouse方法,这里分别对下面两种数据量的案例作了无索引链接的测试:

  • 100万条记录表与1万条记录表链接查询,链接字段无索引。对应selectOrderJoinProduct方法,章节1.3.1 耗时比较中的无索引链接1
  • 100万条记录表与100条记录表链接查询,链接字段无索引。对应selectOrderJoinWarehouse方法,章节1.3.1 耗时比较中的无索引链接2

此时MySQL8的性能优点极大:

  • 100万连1万,mysql8的平均耗时 : 2029毫秒,CPU使用率在100%上下。
  • 100万连1万,mysql5的平均耗时 : 1771556毫秒,CPU使用率在100%上下。
  • 100万连100,mysql8的平均耗时 : 1583毫秒,CPU使用率在100%上下。
  • 100万连100,mysql5的平均耗时 : 17042毫秒,CPU使用率在100%上下。

为什么链接字段无索引的状况下,MySQL8的优点如此巨大?这就是MySQL8开始支持的哈希链接的功劳了。

selectOrderJoinProduct在mysql8的执行计划:

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   9999 |   100.00 | NULL                                       |
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

mysql> 
mysql> explain format=tree SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                        |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Inner hash join (a.product_number = b.product_number)  (cost=994283853.13 rows=994265578)
    -> Table scan on a  (cost=2.72 rows=994365)
    -> Hash
        -> Table scan on b  (cost=1057.73 rows=9999)
 |
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

selectOrderJoinProduct在mysql5的执行计划:

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   9999 |   100.00 | NULL                                               |
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

咱们能够清楚的看到,MySQL8使用的是hash join,而MySQL5使用的是Block Nested Loop,块嵌套循环,BNL,该策略从MySQL 8.0.20开始再也不使用。

hash join就是将较小的那张表的数据集作成哈希数据集,而后遍历较大的表的数据集,对每条记录,根据链接字段直接从哈希数据集中获取小表对应记录。其时间复杂度为 O(m+n),m与n分别是大表与小表的数据量。

BNL就是双层嵌套循环,一般将小表做为主表,遍历其数据集,对每条记录再遍历大表数据集查找对应记录。其时间复杂度为O(m*n)

即便链接的两张表有其余非链接字段上的过滤条件,且有索引可使用,大部分状况下也依然是hash join效率更高。

4.7 独立子查询

在MySQL中执行如下使用IN的独立子查询,并查看其执行计划:

SELECT ord_number, warehouse_number from tb_order where warehouse_number in (SELECT warehouse_number from tb_warehouse);

explain SELECT ord_number, warehouse_number from tb_order where warehouse_number in (SELECT warehouse_number from tb_warehouse);

show warnings;
注意查看完执行计划以后,要当即执行 show warnings;,否则看不到 semi join半链接优化。

查看执行结果,MySQL8优点极大。查看执行计划会发现,缘由仍是哈希链接的使用。

  • mysql8的耗时 : 0.84秒。
  • mysql5的耗时 : 14.69秒。

mysql5的执行结果及其执行计划:

-- 14.69秒
SELECT ord_number, warehouse_number from tb_order where warehouse_number in (SELECT warehouse_number from tb_warehouse);

mysql> explain SELECT ord_number, warehouse_number from tb_order where warehouse_number in (SELECT warehouse_number from tb_warehouse);
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type  | table        | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                              |
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE       | <subquery2>  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   NULL |   100.00 | NULL                                               |
|  1 | SIMPLE       | tb_order     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
|  2 | MATERIALIZED | tb_warehouse | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    100 |   100.00 | NULL                                               |
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                    |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `db_mysql_test1`.`tb_order`.`ord_number` AS `ord_number`,`db_mysql_test1`.`tb_order`.`warehouse_number` AS `warehouse_number` from `db_mysql_test1`.`tb_order` semi join (`db_mysql_test1`.`tb_warehouse`) where (`db_mysql_test1`.`tb_order`.`warehouse_number` = `<subquery2>`.`warehouse_number`) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
能够看到,对于使用 IN的独立子查询,MySQL5选择了 Semi-join半链接和 Materialization物化的优化策略,将子查询改成半链接,并物化为临时表。

但并非说作了半链接物化优化就必定更快,优化器会根据具体的表统计信息(表结构与表数据量等)估算并比较不一样的优化策略,选择一个估算性能表现最好的策略。

同时咱们应该注意到,虽然IN语句作了必定的优化,但tb_order与物化的临时表之间链接方式依然是Block Nested Loop,该语句依然较慢的缘由主要是这个。

mysql8的执行结果及其执行计划:

-- 0.84秒
SELECT ord_number, warehouse_number from tb_order where warehouse_number in (SELECT warehouse_number from tb_warehouse);

mysql> explain SELECT ord_number, warehouse_number from tb_order where warehouse_number in (SELECT warehouse_number from tb_warehouse);
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
| id | select_type  | table        | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                      |
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
|  1 | SIMPLE       | <subquery2>  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   NULL |   100.00 | NULL                                       |
|  1 | SIMPLE       | tb_order     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (hash join) |
|  2 | MATERIALIZED | tb_warehouse | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    100 |   100.00 | NULL                                       |
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

mysql> 
mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                    |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1003 | /* select#1 */ select `db_mysql_test1`.`tb_order`.`ord_number` AS `ord_number`,`db_mysql_test1`.`tb_order`.`warehouse_number` AS `warehouse_number` from `db_mysql_test1`.`tb_order` semi join (`db_mysql_test1`.`tb_warehouse`) where (`db_mysql_test1`.`tb_order`.`warehouse_number` = `<subquery2>`.`warehouse_number`) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
MySQL8也作了半链接 semi join和物化 MATERIALIZED优化,但再也不使用BNL,而是换成了 hash join

4.8 关联子查询

在MySQL中执行如下使用EXISTS的关联子查询,并查看其执行计划:

SELECT ord_number, warehouse_number from tb_order where EXISTS (SELECT * from tb_warehouse where warehouse_number = tb_order.warehouse_number );

explain SELECT ord_number, warehouse_number from tb_order where EXISTS (SELECT * from tb_warehouse where warehouse_number = tb_order.warehouse_number );

show warnings;
注意查看完执行计划以后,要当即执行 show warnings;,否则看不到 semi join半链接优化。

查看执行结果,MySQL8优点极大。查看执行计划会发现,缘由主要是对EXISTS子句进行半链接+物化优化后可使用哈希链接。

  • mysql8的耗时 : 0.83秒。
  • mysql5的耗时 : 18.02秒。

mysql5中的执行结果和执行计划:

-- 18.02秒+
SELECT ord_number, warehouse_number from tb_order where EXISTS (SELECT * from tb_warehouse where warehouse_number = tb_order.warehouse_number );

mysql> explain SELECT ord_number, warehouse_number from tb_order where EXISTS (SELECT * from tb_warehouse where warehouse_number = tb_order.warehouse_number );
+----+--------------------+--------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type        | table        | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+--------------------+--------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | PRIMARY            | tb_order     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |   100.00 | Using where |
|  2 | DEPENDENT SUBQUERY | tb_warehouse | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    100 |    10.00 | Using where |
+----+--------------------+--------------+------------+------+---------------+------+---------+------+--------+----------+-------------+
2 rows in set, 2 warnings (0.00 sec)

mysql> show warnings;
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                                                                     |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1276 | Field or reference 'db_mysql_test1.tb_order.warehouse_number' of SELECT #2 was resolved in SELECT #1                                                                                                                                                                                                                                                                        |
| Note  | 1003 | /* select#1 */ select `db_mysql_test1`.`tb_order`.`ord_number` AS `ord_number`,`db_mysql_test1`.`tb_order`.`warehouse_number` AS `warehouse_number` from `db_mysql_test1`.`tb_order` where exists(/* select#2 */ select 1 from `db_mysql_test1`.`tb_warehouse` where (`db_mysql_test1`.`tb_warehouse`.`warehouse_number` = `db_mysql_test1`.`tb_order`.`warehouse_number`)) |
+-------+------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
能够看到,对于使用 EXISTS的关联子查询,MySQL5没有作 Semi-join Materialization优化,相比 IN语句性能略有不如。

mysql8中的执行结果和执行计划:

-- 0.83秒+
SELECT ord_number, warehouse_number from tb_order where EXISTS (SELECT * from tb_warehouse where warehouse_number = tb_order.warehouse_number );

mysql> explain SELECT ord_number, warehouse_number from tb_order where EXISTS (SELECT * from tb_warehouse where warehouse_number = tb_order.warehouse_number );
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
| id | select_type  | table        | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                      |
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
|  1 | SIMPLE       | <subquery2>  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   NULL |   100.00 | NULL                                       |
|  1 | SIMPLE       | tb_order     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (hash join) |
|  2 | MATERIALIZED | tb_warehouse | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    100 |   100.00 | NULL                                       |
+----+--------------+--------------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
3 rows in set, 2 warnings (0.00 sec)

mysql> 
mysql> show warnings;
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                                                                                                                                                                    |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1276 | Field or reference 'db_mysql_test1.tb_order.warehouse_number' of SELECT #2 was resolved in SELECT #1                                                                                                                                                                                                                       |
| Note  | 1003 | /* select#1 */ select `db_mysql_test1`.`tb_order`.`ord_number` AS `ord_number`,`db_mysql_test1`.`tb_order`.`warehouse_number` AS `warehouse_number` from `db_mysql_test1`.`tb_order` semi join (`db_mysql_test1`.`tb_warehouse`) where (`db_mysql_test1`.`tb_order`.`warehouse_number` = `<subquery2>`.`warehouse_number`) |
+-------+------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
性能相比mysql5有极大提高,但咱们要注意,该案例性能提高的最主要缘由是因为半链接优化,致使可以使用 hash join了。

4.9 倒序排序

MySQL8真正支持建立倒序索引,而不是之前那样伪装建立倒序索引,但实际仍是正序索引。这使得某些场景下性能有所提高。

好比这样的案例,对tb_order表查询时,使用custom_numberproduct_number排序,其中product_number须要倒序。正常来讲,应该建立下面的索引:

CREATE INDEX `tb_order_idx01` ON `db_mysql_test1`.`tb_order` (`custom_number`, `product_number` DESC);

但一样的索引,在MySQL8中生效,有效提升了性能;而在MySQL5中并未生效,性能依然不高。

  • 百万数据混合排序在mysql8的耗时 : 0.44秒。
  • 百万数据混合排序在mysql5的耗时 : 1.34秒。

mysql5中执行:

-- 删除倒序索引
mysql> alter table tb_order drop index tb_order_idx01;

mysql> explain SELECT custom_number, product_number from tb_order order by custom_number, product_number DESC ;
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | tb_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |   100.00 | Using filesort |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

-- 建立倒序索引,自己也是组合索引,部分升序,部分降序
CREATE INDEX `tb_order_idx01` ON `db_mysql_test1`.`tb_order` (`custom_number`, `product_number` DESC);

mysql> explain SELECT custom_number, product_number from tb_order order by custom_number, product_number DESC ;
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+--------+----------+-----------------------------+
| id | select_type | table    | partitions | type  | possible_keys | key            | key_len | ref  | rows   | filtered | Extra                       |
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+--------+----------+-----------------------------+
|  1 | SIMPLE      | tb_order | NULL       | index | NULL          | tb_order_idx01 | 164     | NULL | 994365 |   100.00 | Using index; Using filesort |
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+--------+----------+-----------------------------+
1 row in set, 1 warning (0.00 sec)

-- 查询100万条数据须要 1.34秒
mysql> SELECT custom_number, product_number from tb_order order by custom_number, product_number DESC ;
  1. 建立倒序索引前,使用filesort,性能一般比使用index要低。
  2. 建立倒序索引后,只有正序字段使用index,倒序部分依然要使用filesort,由于MySQL5的倒序索引是假的。

mysql8中执行:

-- 删除倒序索引
mysql> alter table tb_order drop index tb_order_idx01;

mysql> explain SELECT custom_number, product_number from tb_order order by custom_number, product_number DESC ;
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
|  1 | SIMPLE      | tb_order | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |   100.00 | Using filesort |
+----+-------------+----------+------------+------+---------------+------+---------+------+--------+----------+----------------+
1 row in set, 1 warning (0.00 sec)

-- 建立倒序索引
CREATE INDEX `tb_order_idx01` ON `db_mysql_test1`.`tb_order` (`custom_number`, `product_number` DESC);

mysql> explain SELECT custom_number, product_number from tb_order order by custom_number, product_number DESC ;
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+--------+----------+-------------+
| id | select_type | table    | partitions | type  | possible_keys | key            | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | tb_order | NULL       | index | NULL          | tb_order_idx01 | 164     | NULL | 100000 |   100.00 | Using index |
+----+-------------+----------+------------+-------+---------------+----------------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

-- 查询100万条数据须要 0.44秒
mysql> SELECT custom_number, product_number from tb_order order by custom_number, product_number DESC ;
  1. 建立倒序索引前,使用filesort,性能一般比使用index要低。
  2. 建立倒序索引后,所有使用index,倒序索引生效。

4.10 索引链接与哈希链接的性能对比

如今咱们知道,哈希链接只在链接字段上没有任何索引时起效,大部分业务场景里,链接字段上都是有各类索引的,这时Mysql使用的是索引链接,即,遍历主表数据结果集,对每一条记录,使用索引去副表结果集中查找。即,Nested Loop + 索引。注意,这不是Block Nested LoopBNL块嵌套循环,BNL是之前的Mysql在链接字段上没有索引时采用的链接策略。

目前mysql在链接字段上有索引的状况下,默认使用索引链接。但这并非说索引链接就必定比哈希链接快。这取决于具体的数据量和表结构。

<!-- ### 4.10.1 MySQL8的哈希链接有时比MySQL5的索引链接快
mysql5中的表现:

-- tb_order有100万数据,tb_product有1万数据,耗时在半小时左右
SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   9999 |   100.00 | NULL                                               |
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)


-- 添加惟一索引
CREATE UNIQUE INDEX `tb_product_unique01` ON `db_mysql_test1`.`tb_product` (`product_number`);

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+--------+---------------------+---------------------+---------+---------------------------------+--------+----------+-------+
| id | select_type | table | partitions | type   | possible_keys       | key                 | key_len | ref                             | rows   | filtered | Extra |
+----+-------------+-------+------------+--------+---------------------+---------------------+---------+---------------------------------+--------+----------+-------+
|  1 | SIMPLE      | a     | NULL       | ALL    | NULL                | NULL                | NULL    | NULL                            | 994365 |   100.00 | NULL  |
|  1 | SIMPLE      | b     | NULL       | eq_ref | tb_product_unique01 | tb_product_unique01 | 82      | db_mysql_test1.a.product_number |      1 |   100.00 | NULL  |
+----+-------------+-------+------------+--------+---------------------+---------------------+---------+---------------------------------+--------+----------+-------+
2 rows in set, 1 warning (0.00 sec)

-- 2.26秒
SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

mysql8中的表现:

-- tb_order有100万数据,tb_product有1万数据,耗时在1.3秒左右
SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

mysql> explain SELECT a.ord_number, a.ord_status, a.order_time, b.product_number, b.product_name FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra                                      |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   9999 |   100.00 | NULL                                       |
|  1 | SIMPLE      | a     | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

4.10.1 mysql8强制开启或关闭hash join

-- 添加惟一索引
CREATE UNIQUE INDEX `tb_product_unique01` ON `db_mysql_test1`.`tb_product` (`product_number`);

-- 链接字段上有索引时,默认不使用hash join
explain SELECT count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

-- mysql8.0.19以前的版本应该支持这种强制使用 hashjoin的写法,但从MySQL 8.0.19开始应该就不支持了。
explain SELECT /*+ HASH_JOIN(a,b)  */ count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

-- 要强制使用hash join的话,必须让索引不起做用,能够选用下面任意一种方式:
explain SELECT /*+ NO_JOIN_INDEX(b tb_product_unique01)  */ count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
explain SELECT /*+ NO_INDEX(b tb_product_unique01)  */ count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
SELECT count(*) FROM tb_order a inner join tb_product b ignore index(tb_product_unique01) on(a.product_number = b.product_number);
官方文档说能够用 NO_BNL强制走Hash Join,可是我试了下,若是链接字段没有索引的话,默认就走 hash join了;若是有索引的话,那么必须忽略掉该索引才会走hash join。

BNLNO_BNL原本是用来控制是否使用block nested loop块嵌套循环的。官方文档说从8.0.20开始,Mysql已经再也不使用block nested loop块嵌套循环了,而后又不能当即删除这两个hint语法,因此就用来强制走不走hash join了。。。但实际上没啥用,由于有索引的话优先用的是索引链接,没索引默认用hash join。即,只要索引生效,设置NO_BNL就是徒劳的,并不会走hash join。想在有索引的状况下强制走hash join的话,就必须让索引不起做用。

4.10.2 哈希链接与索引链接性能比较

有的案例,哈希链接慢一点;有的案例,哈希链接快一点。不能一律而论。

  • 案例1,在该测试条件(大表100万,小表1万,链接字段有惟一索引)下,hash join比索引链接稍慢:
-- 0.44秒
SELECT count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

-- 0.88秒
SELECT /*+ NO_JOIN_INDEX(b tb_product_unique01)  */ count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);

mysql> explain SELECT count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+--------+---------------------+---------------------+---------+---------------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys       | key                 | key_len | ref                             | rows   | filtered | Extra       |
+----+-------------+-------+------------+--------+---------------------+---------------------+---------+---------------------------------+--------+----------+-------------+
|  1 | SIMPLE      | a     | NULL       | index  | NULL                | tb_order_idx01      | 164     | NULL                            | 994365 |   100.00 | Using index |
|  1 | SIMPLE      | b     | NULL       | eq_ref | tb_product_unique01 | tb_product_unique01 | 82      | db_mysql_test1.a.product_number |      1 |   100.00 | Using index |
+----+-------------+-------+------------+--------+---------------------+---------------------+---------+---------------------------------+--------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

mysql> 
mysql> 
mysql> explain SELECT /*+ NO_JOIN_INDEX(b tb_product_unique01)  */ count(*) FROM tb_order a inner join tb_product b on(a.product_number = b.product_number);
+----+-------------+-------+------------+-------+---------------+---------------------+---------+------+--------+----------+---------------------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key                 | key_len | ref  | rows   | filtered | Extra                                                   |
+----+-------------+-------+------------+-------+---------------+---------------------+---------+------+--------+----------+---------------------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | index | NULL          | tb_product_unique01 | 82      | NULL |   9999 |   100.00 | Using index                                             |
|  1 | SIMPLE      | a     | NULL       | index | NULL          | tb_order_idx01      | 164     | NULL | 994365 |    10.00 | Using where; Using index; Using join buffer (hash join) |
+----+-------------+-------+------------+-------+---------------+---------------------+---------+------+--------+----------+---------------------------------------------------------+
2 rows in set, 1 warning (0.00 sec)
  • 案例2,大表100万,小表10万,链接字段有惟一索引,hash join比索引链接稍快一点。
-- 去除其余对性能有加成的索引
alter table tb_order drop index tb_order_idx01;

-- tb_custom.custom_number上有惟一索引 tb_custom_unique01,默认使用索引链接
-- 1.52秒
SELECT count(*) FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);

-- 经过hint语法NO_JOIN_INDEX让语句执行时,再也不使用目标索引tb_custom_unique01作索引链接
-- 1.12秒
SELECT /*+ NO_JOIN_INDEX(b tb_custom_unique01)  */ count(*) FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);

mysql> explain SELECT count(*) FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys      | key                | key_len | ref                            | rows   | filtered | Extra       |
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------------+
|  1 | SIMPLE      | a     | NULL       | ALL    | NULL               | NULL               | NULL    | NULL                           | 994365 |   100.00 | NULL        |
|  1 | SIMPLE      | b     | NULL       | eq_ref | tb_custom_unique01 | tb_custom_unique01 | 82      | db_mysql_test1.a.custom_number |      1 |   100.00 | Using index |
+----+-------------+-------+------------+--------+--------------------+--------------------+---------+--------------------------------+--------+----------+-------------+
2 rows in set, 1 warning (0.00 sec)

mysql> explain SELECT /*+ NO_JOIN_INDEX(b tb_custom_unique01)  */ count(*) FROM tb_order a inner join tb_custom b on(a.custom_number = b.custom_number);
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+--------+----------+--------------------------------------------+
| id | select_type | table | partitions | type  | possible_keys | key                | key_len | ref  | rows   | filtered | Extra                                      |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+--------+----------+--------------------------------------------+
|  1 | SIMPLE      | b     | NULL       | index | NULL          | tb_custom_unique01 | 82      | NULL |  99522 |   100.00 | Using index                                |
|  1 | SIMPLE      | a     | NULL       | ALL   | NULL          | NULL               | NULL    | NULL | 994365 |    10.00 | Using where; Using join buffer (hash join) |
+----+-------------+-------+------------+-------+---------------+--------------------+---------+------+--------+----------+--------------------------------------------+
2 rows in set, 1 warning (0.00 sec)

5、mysql-test程序测试结果记录

mysql-test程序分别对MySQL8MySQL5进行了数轮测试,统计以下:

具体测试项目请参考程序代码。

5.1 mysql8测试结果

轮次 1 2 3 4 5 平均值
prepareData 耗时(毫秒) 10258 10892 10871 10502 9413 10387
truncateTables 耗时(毫秒) 553 569 581 527 552 556
insertOrder 耗时(毫秒) 35756 31831 34257 30403 34697 33389
insertCustom 耗时(毫秒) 3349 2781 2795 2613 2944 2896
insertProduct 耗时(毫秒) 317 231 275 198 294 263
insertWarehouse 耗时(毫秒) 6 15 8 14 8 10
selectOrders conn 耗时(毫秒) 1 3 3 6 3 3
selectOrders executeQuery 耗时(毫秒) 1399 1123 1150 1057 1180 1182
selectOrders ResultSet遍历 耗时(毫秒) 104 76 80 78 85 85
selectOrders 总耗时(毫秒) 1507 1205 1234 1143 1269 1272
selectOrders 件数 1000000 1000000 1000000 1000000 1000000 1000000
selectCustoms conn 耗时(毫秒) 2 2 3 2 2 2
selectCustoms executeQuery 耗时(毫秒) 124 140 115 76 107 112
selectCustoms ResultSet遍历 耗时(毫秒) 51 66 74 18 50 52
selectCustoms 总耗时(毫秒) 179 210 193 98 161 168
selectCustoms 件数 100000 100000 100000 100000 100000 100000
selectProducts conn 耗时(毫秒) 4 4 4 2 5 4
selectProducts executeQuery 耗时(毫秒) 15 13 12 9 10 12
selectProducts ResultSet遍历 耗时(毫秒) 5 13 12 2 7 8
selectProducts 总耗时(毫秒) 25 31 29 15 23 25
selectProducts 件数 10000 10000 10000 10000 10000 10000
selectWarehouses conn 耗时(毫秒) 3 3 3 3 3 3
selectWarehouses executeQuery 耗时(毫秒) 1 1 1 1 1 1
selectWarehouses ResultSet遍历 耗时(毫秒) 0 0 0 0 0 0
selectWarehouses 总耗时(毫秒) 5 5 5 4 5 5
selectWarehouses 件数 100 100 100 100 100 100
selectOrderJoinCustom conn 耗时(毫秒) 3 3 3 2 3 3
selectOrderJoinCustom executeQuery 耗时(毫秒) 3586 3506 3684 3084 2816 3335
selectOrderJoinCustom ResultSet遍历 耗时(毫秒) 66 99 102 52 73 78
selectOrderJoinCustom 总耗时(毫秒) 3657 3611 3791 3140 2894 3419
selectOrderJoinCustom 件数 1000000 1000000 1000000 1000000 1000000 1000000
selectOrderJoinProduct conn 耗时(毫秒) 2 3 3 2 2 2
selectOrderJoinProduct executeQuery 耗时(毫秒) 2424 1704 1943 1709 2364 2029
selectOrderJoinProduct ResultSet遍历 耗时(毫秒) 55 74 69 51 56 61
selectOrderJoinProduct 总耗时(毫秒) 2482 1782 2016 1763 2424 2093
selectOrderJoinProduct 件数 1000000 1000000 1000000 1000000 1000000 1000000
selectOrderJoinWarehouse conn 耗时(毫秒) 2 2 2 2 2 2
selectOrderJoinWarehouse executeQuery 耗时(毫秒) 1466 2269 1542 1107 1529 1583
selectOrderJoinWarehouse ResultSet遍历 耗时(毫秒) 62 135 74 52 50 75
selectOrderJoinWarehouse 总耗时(毫秒) 1531 2411 1619 1162 1582 1661
selectOrderJoinWarehouse 件数 1000000 1000000 1000000 1000000 1000000 1000000
对mysql8进行了5轮测试,取其平均值。

5.2 mysql5测试结果

轮次 1 2 3 平均值
prepareData 耗时(毫秒) 12377 9073 9204 10218
truncateTables 耗时(毫秒) 627 475 451 518
insertOrder 耗时(毫秒) 24152 24193 21994 23446
insertCustom 耗时(毫秒) 1912 1916 1941 1923
insertProduct 耗时(毫秒) 137 147 156 147
insertWarehouse 耗时(毫秒) 4 4 8 5
selectOrders conn 耗时(毫秒) 2 3 3 3
selectOrders executeQuery 耗时(毫秒) 1181 1513 1238 1311
selectOrders ResultSet遍历 耗时(毫秒) 112 96 106 105
selectOrders 总耗时(毫秒) 1297 1614 1349 1420
selectOrders 件数 1000000 1000000 1000000 1000000
selectCustoms conn 耗时(毫秒) 2 2 2 2
selectCustoms executeQuery 耗时(毫秒) 82 113 116 104
selectCustoms ResultSet遍历 耗时(毫秒) 28 23 31 27
selectCustoms 总耗时(毫秒) 114 141 151 135
selectCustoms 件数 100000 100000 100000 100000
selectProducts conn 耗时(毫秒) 2 3 4 3
selectProducts executeQuery 耗时(毫秒) 13 10 17 13
selectProducts ResultSet遍历 耗时(毫秒) 3 2 6 4
selectProducts 总耗时(毫秒) 20 15 29 21
selectProducts 件数 10000 10000 10000 10000
selectWarehouses conn 耗时(毫秒) 2 2 3 2
selectWarehouses executeQuery 耗时(毫秒) 0 0 1 0
selectWarehouses ResultSet遍历 耗时(毫秒) 0 0 0 0
selectWarehouses 总耗时(毫秒) 4 3 4 4
selectWarehouses 件数 100 100 100 100
selectOrderJoinCustom conn 耗时(毫秒) 2 2 2 2
selectOrderJoinCustom executeQuery 耗时(毫秒) 3156 2548 2876 2860
selectOrderJoinCustom ResultSet遍历 耗时(毫秒) 47 52 61 53
selectOrderJoinCustom 总耗时(毫秒) 3207 2604 2941 2917
selectOrderJoinCustom 件数 1000000 1000000 1000000 1000000
selectOrderJoinProduct conn 耗时(毫秒) 2 2 2 2
selectOrderJoinProduct executeQuery 耗时(毫秒) 1655023 1756847 1902797 1771556
selectOrderJoinProduct ResultSet遍历 耗时(毫秒) 43 51 222 105
selectOrderJoinProduct 总耗时(毫秒) 1655069 1756902 1903023 1771665
selectOrderJoinProduct 件数 1000000 1000000 1000000 1000000
selectOrderJoinWarehouse conn 耗时(毫秒) 2 2 7 4
selectOrderJoinWarehouse executeQuery 耗时(毫秒) 16264 16030 18831 17042
selectOrderJoinWarehouse ResultSet遍历 耗时(毫秒) 35 50 609 231
selectOrderJoinWarehouse 总耗时(毫秒) 16303 16083 19448 17278
selectOrderJoinWarehouse 件数 1000000 1000000 1000000 1000000
mysql5的 selectOrderJoinProduct实在太过耗时,这里只测试了三轮。
相关文章
相关标签/搜索