MySQL 初級優化

1. 加載官方數據集、explain 指標、分頁常用優化;

安裝 MySQL 測試數據集:https://github.com/datacharmer/test_db

# 下載資源
cd /usr/local/src
git clone https://github.com/datacharmer/test_db

# 導入
mysql uroot -pasdf < employees.sql

explain 命令:優化 SQL 語句、查看 SQL 是如何執行、優化指標

# 文檔 2:查找關鍵字 EXPLAIN Join Types
# 結果集依次好壞:
# system > const > eq_ref > ref > fulltext > ref_or_null 
# > index_merge > unique_subquery > index_subquery > range > index > ALL

# 舉例說明:
EXPLAIN select * from employees
# type 就是指標,執行上面 SQL 語句的 type 爲 ALL
# 問題 1:SQL 查詢語句用 * 是不好的,在性能上和帶寬上都是有問題的。用到什麼字段寫什麼字段

# 優化:根據主鍵排序
EXPLAIN select * from employees ORDER BY emp_no 
# type 上升爲第二級 index,完成了第一步優化

# 第三級 range:在範圍之內,取出指定範圍內的行
EXPLAIN select * from employees ORDER BY emp_no limit 0, 10
# rows 列:MySQL 預估要檢查的行數,只能供參考,並不是實際的行數
# 以下兩句話效果不一樣
EXPLAIN select * from employees ORDER BY emp_no limit 0,10;
EXPLAIN select * from employees ORDER BY emp_no limit 50,10
# 在做 MySQL 分頁的時候,limit 的第一個參數越大的時候,rows 也會越來越大

分頁常見方法

# 以第二頁爲例(實際開發第二頁不需要這麼做,可以設置個閾值,當分頁大於多少時開始啓用如下 SQL 語句,小於的話直接 limit 就行)
# 這裏用第二頁舉例僅僅是爲了演示方便
# 優化方式:主鍵裏面去取主鍵
# 主鍵大於上一頁的最大值(正排序,越往下越大)
select xx,yy from employees where emp_no >= 
(select emp_no from employees ORDER BY emp_no limit 10,1) 
ORDER BY emp_no limit 10

# 分析
EXPLAIN select emp_no from employees ORDER BY emp_no limit 10,1
# Extra 列,代表 MySQL 完成查詢的所需的額外信息值:比如創建臨時表,使用索引等
# Using index :從**索引樹**中取值而不需要從實際的 rows 來檢索
# Using where: 限制了那些行進行匹配

EXPLAIN select xx,yy from employees where emp_no >= 
(select emp_no from employees ORDER BY emp_no limit 10,1) 
ORDER BY emp_no limit 10
# type 出現了 range,比 index 更加高一層
# range 是在指定的行,通過索引去選擇行

實戰常見做法(場景不同做法不同)

# 在"下一頁"參數中帶上 >= 的數字
# 比如頁碼 1 的 url 是 a.php?page=1    
# 頁碼 2 的參數是 a.php?page=2&no=10010
EXPLAIN select xx,yy from employees where emp_no >= 10010
# 此時 MySQL 運行速度是非常快的

# 另外的做法(優化),不僅僅是從數據庫入手,需要藉助第三方,比如 redis
# 比如以 50 頁爲一段,把各個 50 頁的 ID 值寫入 redis 緩存(hash數據類型)
# 存入前 50 個 ID
hset xxx1 page1 10010,10011,10020
hset xxx2 page2
hset xxx3 page3
# 查詢的時候,使用索引的子查詢(value IN (SELECT key_column FROM ...))
# 分頁時從緩存中取範圍,則進入 range 指標
# 如果刪掉了數據,需要去更新 redis 緩存

# 如果查詢併發高,寫不高,直接把前 20 頁數據放入 redis 緩存中。定期更新緩存

# 藉助類似 elasticsearch 等第三方

# 數據過大,需要分庫分表。使用數據庫中間件

2. 索引優化;

2.1 字符串查詢、BTree 索引;

# 用字符串作爲條件
select count(*) from employees where last_name = 'Brender'
# 查詢出 196 條記錄

# 以下是性能比較低的查詢方式(如果數據有個幾百萬)
# EXPALIN 一下,type 爲 ALL,是最醜陋的一個指標
select * from employees where last_name = 'Brender' limit 10

# 繼續分析
EXPLAIN select xx from employees where last_name = 'Brender' order by emp_no limit 10;
# order by 主鍵索引,type 爲 index,需要繼續優化
# last_name 經常要用來查詢,所以建立索引,索引名自定義,欄位名和字段名匹配
# 索引類型爲 normal,索引方式選擇 BTREE(不能選 HASH)

## BTree 索引(平衡多路查找樹)
# 此時再 EXPLAIN,type 爲 ref
# 參考:https://dev.mysql.com/doc/refman/5.7/en/explain-output.html#jointype_ref
# 通過 BTree,可以快速定位行

# Extra 爲 Using index condition
# possible_keys:可能使用哪個索引在該表中找到行(這裏是 last_name)
# key:實際決定使用的索引(這裏是 last_name)
# key_len:使用索引(字節)長度,int 佔 4 字節,而varchar(n) 需要原長度 n+2,如果允許空 n+3
# 一般索引字段不要爲空
# 字符編碼 latin1 的 1 字節是 1 長度,gbk 是 1:2,utf8 是 1:3
# select_type:SIMPLE 最簡單的查詢(不包含子查詢或 union)

# 各個引擎支持的索引類型(如下圖)
# https://dev.mysql.com/doc/refman/5.7/en/create-index.html

在這裏插入圖片描述

2.2 唯一索引、組合索引;

唯一索引

# 不重複的字段建立索引,就是唯一索引
# 在 employees 表中加一個字段 ids,代表是身份證,類型 char(18)
# 執行以下 SQL
update employees set ids = 31010119901212 + emp_no

# 如果要查詢身份證
EXPLAIN select xx from employees where ids = '31010119911218'
# 這是完全沒優化的代碼,type 爲 ALL

# 分別給 ids 創建 普通索引 和 唯一索引
# 普通索引的 type 爲 ref
# 唯一索引 type 進一步提升爲 const
# 比普通索引更快,而且重複值無法插入

count(*)

# 文檔
# https://dev.mysql.com/doc/refman/5.7/en/group-by-functions.html#function_count
# count(*) 在 MyISAM 模式下非常快,行數已經保存在了引擎裏,直接取出來並不需要去掃描表
# 在 INNODB 下,可能會因爲其它連接在做事務而產生不一樣的結果

# count(*) 代表統計所有數據的行數  
select count(*) from employees 

# count(ids) 這個統計是不包含 ids = null 的數據的
select count(ids) from employees 
# 如下操作後再 count(ids),會發現記錄少了一條
update employees set ids = null where emp_no = 10001
# 之後如果有需求,查詢填過身份證的條數,就直接 count(ids)

聯合索引

# 場景:查詢出僱傭時間 hire_date 在某個時間的數據
# 由於僱用時間會重複,不能建唯一索引
# 如果 hire_date 需要經常查詢,需要對它創建一個普通索引
EXPLAIN select xx from employees where hire_date = '1986-12-01' 
order by emp_no LIMIT 0,50
# type 爲 ref

# 追加需求(ids 和 hire_date 都有索引)
EXPLAIN select xx from employees where hire_date >= '1986-12-01' 
order by hire_date, ids LIMIT 0,50
# 此時 type 降級爲 ALL
# 查看概況 Creating sort index 需要花去 99% 以上的時間
# 如果經常需要對這兩個字段進行排序的話
# 此時需要創建聯合索引,創建索引的時候把 hire_date 和 ids (字段先後順序和查詢語句相同)同時勾上
# type 升級到 range
# possible keys 爲 hire_date, hire_date_2
# key 爲 hire_date_2(hire_date_2 不是字段名,是索引名)
# 聯合索引不能隨意創建,創建的時候字段順序要要和 SQL 語句的字段順序一樣
# 查詢條件的字段也是(注意是 where hire_date >=,聯合索引也是 hire_date 在前)
# 如果查詢語句是 where ids >=,那需要調整聯合索引字段爲 ids,hire_date

# 不要所有列都創建索引,否則 insert 和 update 就會很慢,因爲要創建或者更新索引

2.3 Limit 優化;

官方示例數據庫
在這裏插入圖片描述

# 如果不加 limit,有索引 type 還是 ALL,key爲 NULL
# 加上 limit 50,type 升級爲 range
EXPLAIN select * from employees where hire_date > '1985-02-18' limit 50

# 接下去用到 salaries 表
# 需求:查詢出 hire_date > '1985-02-18' 的員工的薪水情況,要求是按 emp_no 倒排序
# 原始 SQL 如下
EXPLAIN select a.emp_no, a.salary,from_date from salaries a 
left join employees b 
on a.emp_no = b.emp_no
where b.hire_date > '1985-02-18' 
order by a.emp_no limit 50
# 第一行 b 表 type 爲 range,Extra 包含 Using temporary(臨時表),Using filesort(額外排序方式)
# 但是出現以上兩項說明需要優化
# 第二行 a 表 type 爲 ref 
# 實際運行以上的 SQL 語句,幾乎無法運行成功
# 修改寫法:
EXPLAIN select a.emp_no,a.salary,from_date from salaries a 
left join employees b 
on a.emp_no=b.emp_no
where hire_date > '1985-02-18' 
order by b.emp_no limit 50
# 第一行 b 表 type 爲 index,row 爲 10,Extra 爲 using where
# 第二行 a 表 type 爲 ref
# 所以有時候不能光看 type 的指標,還要注意 Extra

3. 利用子查詢、右連接進行聚合查詢優化。

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