數棧SQL優化案例:隱式轉換

數棧是雲原生—站式數據中臺PaaS,咱們在github和gitee上有一個有趣的開源項目:FlinkX,FlinkX是一個基於Flink的批流統一的數據同步工具,既能夠採集靜態的數據,也能夠採集實時變化的數據,是全域、異構、批流一體的數據同步引擎。你們喜歡的話請給咱們點個star!star!star!mysql

github開源項目:https://github.com/DTStack/flinkxgit

gitee開源項目:https://gitee.com/dtstack_dev_0/flinkxgithub

MySQL是當下最流行的關係型數據庫之一,互聯網高速發展的今天,MySQL數據庫在電商、金融等諸多行業的生產系統中被普遍使用。sql

在實際的開發運維過程當中,想必你們也經常會碰到慢SQL的困擾。一條性能很差的SQL,每每會帶來過大的性能開銷,進而引發整個操做系統資源的過分使用,甚至形成會話堆積,引起線上故障。數據庫

而在SQL調優的場景中,一類比較常見的問題,就是隱式類型轉換。那什麼是隱式轉換呢?app

在MySQL中,當操做符與不一樣類型的操做數一塊兒使用時,會發生類型轉換以使操做數兼容,此時則會發生隱式轉換。出現隱式轉換,每每意味着SQL的執行效率將大幅下降。運維

接下來筆者將結合幾大常見場景,讓你們實際體會什麼是隱式轉換,以及如何去應對出現隱式轉換的狀況,請閱讀如下案例。ide

1、傳遞數據類型和字段類型不一致形成隱式轉換工具

一類比較經典的場景就是傳遞數據類型和字段類型不一致形成的隱式轉換,這種場景也是咱們平時最常遇到的。具體能夠看下下面這個例子:oop

1) 待優化場景

SQL及執行計劃以下:

select * from dt_t1 where emp_no = 41680;

該表索引以下:

key idx_empno (`emp_no`)

2)場景解析

從執行計劃中Type部分:ALL,全表掃描,而沒有走idx_empno索引, 通常這種狀況可能傳遞的數據類型和實際的字段類型不一致,那麼咱們來看下具體的表結構。

root@localhost mysql.sock 5.7.28-log :[employees] 14:48:10>desc employees;
+------------+---------------+------+-----+---------+-------+
| Field      | Type          | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+-------+
| emp_no     | varchar(14)   | NO   | MUL | NULL    |       |
| birth_date | date          | NO   |     | NULL    |       |
| first_name | varchar(14)   | NO   |     | NULL    |       |
| last_name  | varchar(16)   | NO   |     | NULL    |       |
| gender     | enum('M','F') | NO   |     | NULL    |       |
| hire_date  | date          | NO   |     | NULL    |       |
+------------+---------------+------+-----+---------+-------+
6 rows in set (0.00 sec)

表結構中看到該字段類型爲varchar 類型,傳遞字段爲整型,形成隱式轉換不能走索引。

3)場景優化

該SQL可經過簡單改寫來避免出現隱式轉換,以下:

select * from dt_t1 where emp_no='41680';

當傳入數據是與匹配字段一致的varchar類型時,即可以正常使用到索引了,優化效果以下:

2、關聯字段類型不一致形成隱式轉換

除了常量匹配的查詢場景,關聯查詢在關聯字段不一致的狀況下,也會出現隱式轉換。

1) 待優化場景

SELECT  count(*) from t1  as a
JOIN  `t2`  b on a.`id` = b.`alipay_order_no` ;

2)場景解析

從執行計劃中能夠看出被驅動表 b, Extra:Range checked for each record (index map: 0x8)

通常在當咱們看到Range checked for each record (index map: 0x8) 的時候,可能就是發生了隱式轉換,咱們來看下官方文檔是怎麼解釋的。

Range checked for each record (index map: N) (JSON property: message)

MySQL found no good index to use, but found that some of indexes might be used after column values from preceding tables are known. For each row combination in the preceding tables, MySQL checks whether it is possible to use a range or index_merge access method to retrieve rows. This is not very fast, but is faster than performing a join with no index at all. The applicability criteria are as described in Section 8.2.1.2, 「Range Optimization」, and Section 8.2.1.3, 「Index Merge Optimization」, with the exception that all column values for the preceding table are known and considered to be constants.

Indexes are numbered beginning with 1, in the same order as shown by SHOW INDEX for the table. The index map value N is a bitmask value that indicates which indexes are candidates. For example, a value of 0x19 (binary 11001) means that indexes 1, 4, and 5 will be considered.

查看下錶結構:

CREATE TABLE `t2` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `alipay_order_no` varchar(45) DEFAULT NULL,
  xxxx
  PRIMARY KEY (`id`),
  KEY `idx_alipay_order_no_temp` (`alipay_order_no`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2539968 DEFAULT CHARSET=utf8
共返回 1 行記錄,花費 5 ms.
 CREATE TABLE `t1` (
  `id` bigint(20) NOT NULL,
  xxxxxx
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
共返回 1 行記錄,花費 5 ms.

咱們從表結構上面進行觀察到該關聯字段數據 一個是int 類型,一個是varchar 類型。

當發生這種場景的時候咱們應該如何優化呢?

咱們還回來看看下具體的執行計劃,該驅動表爲a,被驅動表b; 關聯條件:a.id = b.alipay_order_no ; 當a 表的字段id 當爲常數傳遞給b.alipay_order_no 的時候,發生column_type 不一致,沒法使用索引,那麼咱們讓a.id 傳遞的 字段類型和b.alipay_order_no 保持一致,就可使用索引了?

3)場景優化

咱們能夠對驅動表的關聯字段進行顯式的類型轉換,讓其與被驅動表關聯字段類型一致。改寫後SQL以下:

SELECT COUNT(*)
FROM `t1`  o
join `t2`  og  ON `o`.`def8`= `og`.`group_id`
WHERE  o.`def1`= 'DG21424956'

2)場景解析

從這個執行計劃中咱們能夠看出第二列表og 中含有using join buffer (Block Nested Loop) ,TYpe=ALL .

通常這種狀況下:using join buffer (Block Nested Loop) ,發生的狀況是 a. 關聯字段沒有索引 b.發生隱式轉換 等

看下具體表結構:

create table t1(
     .....  
   `group_id` varchar(20) NOT NULL,
   PRIMARY KEY (`id`),
   KEY `group_id` (`group_id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8

create table t2(
     .....  
    `def8` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_tr_def1` (`def8`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

咱們從表結構中能夠看出關聯字段都存在索引,但字符集是不同的,t1 utf8,t2 utf8mb4.

3)場景優化

SQL改寫思路和上例相似,咱們對驅動表的關聯字段進行字符集轉換,以下:

SELECT COUNT(*)   FROM `t1`  o
left join `t2` og  ON CONVERT(  o.`def8`  USING utf8 ) = `og`.`group_id`
WHERE  o.`def1`= 'DG21424956

轉換成一致的字符集以後,即可以經過索引進行關聯了

3、校驗規則不一致形成隱式轉換

那麼,只要保證操做符兩側數據類型以及字符集一致,就不會出現隱式轉換嗎?

答案是否認的,由於字符集還有一個很重要的屬性,就是校驗規則,當校驗規則不一致的時候,也是會出現隱式轉換行爲的。具體看下面這個例子:

1) 待優化場景

SELECT *
FROM `t1`
WHERE `uuid` in (SELECT uuid  FROM t2 WHERE project_create_at!= "0000-00-00 00:00:00")

該SQL執行計劃以下:

 

2)場景解析

兩張表的表結構以下:

CREATE TABLE `t1` (
   `id` int(11) NOT NULL AUTO_INCREMENT,  `
   uuid` varchar(128) COLLATE utf8_unicode_ci NOT NULL DEFAULT '' COMMENT 'UUID',
   xxxxxx
 PRIMARY KEY (`id`),
UNIQUE KEY `uuid_idx` (`uuid`)
) ENGINE=InnoDB AUTO_INCREMENT=2343994 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `t2` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `uuid` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '項目uuid',
PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=5408 DEFAULT CHARSET=utf8

咱們從表結構看出,t1表做爲被驅動表uuid是存在惟一索引的,而且關聯字段數據類型以及字符集也都是一致的,可是校驗規則的不一樣致使了這個場景沒法使用到索引。

3)場景優化

咱們能夠經過以下改寫,對驅動表關聯字段的校驗規則進行顯示定義,讓其與被驅動表一致

explain extended
select b.*
from (select  uuid COLLATE utf8_unicode_ci as uuid
from t1 where project_create_at != "0000-00-00 00:00:00") a, t2 b
where a.uuid = b.uuid
+--------------+-----------------------+--------------------+----------------+-----------------------+-------------------+---------------+----------------+-----------------------+
| id           | select_type           | table              | type           | key                   | key_len           | ref           | rows           | Extra                 |
+--------------+-----------------------+--------------------+----------------+-----------------------+-------------------+---------------+----------------+-----------------------+
| 1            | PRIMARY               | <derived2>         | ALL            |                       |                   |               | 51             |                       |
| 1            | PRIMARY               | b                  | eq_ref         | uuid_idx              | 386               | a.uuid        | 1              |                       |
| 2            | DERIVED               | volunteer_patients | range          | idx-project-create-at | 6                 |               | 51             | Using index condition |
+--------------+-----------------------+--------------------+----------------+-----------------------+-------------------+---------------+----------------+-----------------------+
共返回 3 行記錄,花費 4 ms.

能夠看到,改寫後的SQL,正常使用到索引進行字段關聯,這樣就達到了咱們預期的效果。

4、總結

隱式轉換出現的場景主要有字段類型不一致、關聯字段類型不一致、字符集類型不一致或校對規則不一致等。當出現隱式轉換帶來的SQL性能問題時,分析相應場景對症下藥便可。

除此以外,隱式轉換還可能會帶來查詢結果集不許,字符集不一致也會形成主從同步報錯等,所以在實際使用時咱們應當儘可能避免。

相關文章
相關標籤/搜索