Mysql 數據類型優化之:選擇更小的數據類型

1. 版本

1)操作系統版本

cat /proc/version
Linux version 3.10.0-957.5.1.el7.x86_64 ([email protected]) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC) ) #1

2)數據庫版本

mysql --version
mysql  Ver 14.14 Distrib 5.7.22, for linux-glibc2.12 (x86_64) using  EditLine wrapper

 

2. 問題描述

2.1 問題發現

    這是一個朋友跟我諮詢的問題,幫他分析解決這個問題中,我發先這個問題也正好有效的印證了我們常說的mysql 數據類型優化原則,既選擇更小的數據類型(在滿足業務使用的情況下)。在此拿出來跟大家分享一下。他的問題如下

    他在兩張表上進行關聯查詢,如果兩張表都是utf8 字符集那麼查詢會很快。如果兩張表是utf8mb4字符集那麼查詢就比較慢。其實如果朋友是dba 的話,他一看執行計劃就能看出來,是因爲兩種情況下執行計劃不一樣所以導致執行效率不同

下面建兩張測試表,來重現朋友當時的現象
1. 創建兩張字符集爲 utf8 的表
create table test_join_1(id int,name varchar(250)) default character set utf8;
create table test_join_2(id int,name varchar(250)) default character set utf8;

2. 查看sql 的執行計劃
#這裏該sql寫的是否有優化空間,不在我們本次討論範圍
explain select a.* ,b.id from `test_join_1` a left join
  (SELECT name, min(id) id from `test_join_2` b  group by name) b  on a.name=b.name; 
  
explain select a.* ,b.id from `test_join_1` a left join
    ->   (SELECT name, min(id) id from `test_join_2` b  group by name) b  on a.name=b.name; 
+----+-------------+------------+------------+------+---------------+-------------+---------+------------------+------+----------+---------------------------------+
| id | select_type | table      | partitions | type | possible_keys | key         | key_len | ref              | rows | filtered | Extra                           |
+----+-------------+------------+------------+------+---------------+-------------+---------+------------------+------+----------+---------------------------------+
|  1 | PRIMARY     | a          | NULL       | ALL  | NULL          | NULL        | NULL    | NULL             |    1 |   100.00 | NULL                            |
|  1 | PRIMARY     | <derived2> | NULL       | ref  | <auto_key0>   | <auto_key0> | 753     | test_chen.a.name |    2 |   100.00 | NULL                            |
|  2 | DERIVED     | b          | NULL       | ALL  | NULL          | NULL        | NULL    | NULL             |    1 |   100.00 | Using temporary; Using filesort |
+----+-------------+------------+------------+------+---------------+-------------+---------+------------------+------+----------+---------------------------------+
3 rows in set, 1 warning (0.00 sec)

3. 轉換表的字符集爲 utf8mb4
#注意 utf8 字符集轉換成 utf8mb4不會有問題,當時 utf8mb4 向 utf8 轉換不能保證沒有問題
ALTER TABLE test_join_1 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
ALTER TABLE test_join_2 CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;


4. 轉換字符集後再次查看sql 的執行計劃
explain select a.* ,b.id from `test_join_1` a left join
    ->   (SELECT name, min(id) id from `test_join_2` b  group by name) b  on a.name=b.name; 
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                                              |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
|  1 | PRIMARY     | a          | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | NULL                                               |
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    2 |   100.00 | Using where; Using join buffer (Block Nested Loop) |
|  2 | DERIVED     | b          | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    1 |   100.00 | Using temporary; Using filesort                    |
+----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+----------------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

##在使用 utf8 字符集時,mysql 爲派生表創建了一個索引(auto_key0),這樣a表和派生表之間使用 index Nested-Loop Join(NLJ) 方式關聯。在使用 utf8mb4 字符集時 Mysql 使用 Block Nested-Loop Join(BNL) 方式進行關聯。NLJ 比 BNL 訪問方式更高效。
  通過分析執行計劃,相同數據的情況下,第一次執行會比第二次執行更高效,這也符合朋友生產中產生的現象。但是問題是爲什麼使用 utf8字符集時,會給派生表創建索引,使用utf8mb4時就不會呢?其實原因很簡單,代價,mysql 會計算給派生表name列索引的長度(派生表name列長度使用b表中name列長度),b表name列 爲 varchar(250),在utf8和utf8mb4 字符集下計算出的索引長度分別爲 753(utf8 字符集最多使用3個字節表示一個字符,同時 name 列允許爲 null,並且是可變長度類型,所以key_len=250*3+1+2=753) 和 1003(utf8mb4 字符集最多使用4個字節表示一個字符,同時name列允許爲null,並且是可變長度類型,所以key_len=250*4+1+2=1003)

對於 key_len 超過 1000 的列mysql不會爲派生表創建 <auto_key0>,這也告訴我們,不要隨意設置列的長度,在滿足業務的情況下,列的長度是越小越好。


 

2.2 問題原因

    通過上面的例子,我們知道問題的原因是,在不同的字符集下,mysql 對同一個列創建創建索引的長度是不一樣的,當mysql計算在該列上創建索引的長度超過1000的話,就不會對派生表創建索引。

關於索引長度具體怎麼計算可以參考:

https://blog.csdn.net/shaochenshuo/article/details/105210931

 

2.3 問題處理

    按照規範設計和使用數據庫。

MySQL 應該使用更小的數據類型(在滿足業務的情況下),理由如下:
1)更小的數據類型可能佔用的磁盤空間更小,相同的內存空間可以緩存更多的記錄,減少IO操作,減少磁盤空間佔用
2)更小的數據類型意味着CPU可以更快的進行計算。
3)最主要的是,在數據庫執行過程中MySQL有時會建立一些臨時表進行連接排序或去重操作,在這些臨時表中列的長度跟源表中列的長度一致,列的長度越大,性能也就越不好。所以列的類型儘可能的小。

    

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章