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實在太過耗時,這裏只測試了三輪。
相關文章
相關標籤/搜索