MySQL優化

1、SQL語句優化

一、SQL語句優化html

(1)使用limit對查詢結果的記錄進行限定
(2)避免select *,將須要查找的字段列出來
(3)使用鏈接(join)來代替子查詢
(4)拆分大的delete或insert語句mysql

(5)IN中不該包含太多值程序員

  MySQL對IN作了相應的優化,即將IN中的常量所有存儲在一個數組裏面,並且這個數組是排好序的。可是若是數值較多,產生的消耗也是比較大的。再例如:select id from table_name where num in(1,2,3) 對於連續的數值,能用 between 就不要用 in 了;再或者使用鏈接來替換。算法

 (6)SELECT語句務必指明字段名稱sql

  SELECT *增長不少沒必要要的消耗(cpu、io、內存、網絡帶寬);增長了使用覆蓋索引的可能性;當表結構發生改變時,前斷也須要更新。因此要求直接在select後面接上字段名。數據庫

 (7)當只須要一條數據的時候,使用limit 1數組

  --爲了使EXPLAIN中type列達到const類型ruby

 (8)若是排序字段沒有用到索引,就儘可能少排序性能優化

 (9)若是限制條件中其餘字段沒有索引,儘可能少用or網絡

   or兩邊的字段中,若是有一個不是索引字段,而其餘條件也不是索引字段,會形成該查詢不走索引的狀況。不少時候使用 union all 或者是union(必要的時候)的方式來代替「or」會獲得更好的效果。

 (10)儘可能用union all代替union

   union和union all的差別主要是前者須要將結果集合並後再進行惟一性過濾操做,這就會涉及到排序,增長大量的CPU運算,加大資源消耗及延遲。固然,union all的前提條件是兩個結果集沒有重複數據。

 (11)不使用ORDER BY RAND()

 select id from `table_name` 

  order by rand() limit 1000;

上面的sql語句,可優化爲

select id from `table_name` t1 join 
(select rand() * (select max(id) from `table_name`) as nid) t2 
on t1.id > t2.nid limit 1000;

 (11)區分in和exists, no in和no exists

  exists之外層表爲驅動表,IN先執行子查詢;IN適合於外表大而內表小的狀況;EXISTS適合於外表小而內表大的狀況。

  --not in和not exists,推薦使用not exists。

(12)使用合理的分頁方式以提升分頁的效率

  select id,name from table_name limit 866613, 20  調整爲:

  select id,name from table_name where id> 866612 limit 20

  --取前一頁的最大行數的id,而後根據這個最大的id來限制下一頁的起點。

(13)分段查詢

(14)避免在where子句中對字段進行null值判斷

(15)like子句中,不建議使用%前綴模糊查詢,會致使索引失效

  --對於相似與%name%,建議將對應字段的普通索引替換爲全文索引,like替換爲使用match

(16)避免在where子句中對字段進行表達式操做,會致使索引失效

(17)避免隱式類型轉換

(18)聯合索引要遵照最左前綴法則

(19)必要時可使用force index來強制查詢走某個索引

(20)聯合索引,若是存在範圍查詢,好比between,>,<等條件時,會形成後面的索引字段失效。

(21)union優化

  • LEFT JOIN A表爲驅動表
  • INNER JOIN MySQL會自動找出那個數據少的表做用驅動表
  • RIGHT JOIN B表爲驅動表

  1)儘可能使用inner join,避免left join

  2)被驅動表的索引字段做爲on的限制字段

  3)利用小表驅動大表

  4)STRAIGHT_JOIN強制驅動表的順序

  二、SQL優化之 Mysql錯誤用法

  2.1  隱式轉換

SQL語句中查詢變量和字段定義類型不匹配是另外一個常見的錯誤。好比下面的語句:

mysql> explain extended SELECT * > FROM my_balance b > WHERE b.bpn = 14000000123 > AND b.isverified IS NULL ; mysql> show warnings; | Warning | 1739 | Cannot use ref access on index 'bpn' due to type or collation conversion on field 'bpn' 

其中字段bpn的定義爲varchar(20),MySQL的策略是將字符串轉換爲數字以後再比較。函數做用於表字段,索引失效。

上述狀況多是應用程序框架自動填入的參數,而不是程序員的原意。如今應用框架不少很繁雜,使用方便的同時也當心它可能給本身挖坑。

  2.3  關聯更新、刪除

雖然MySQL5.6引入了物化特性,但須要特別注意它目前僅僅針對查詢語句的優化。對於更新或刪除須要手工重寫成JOIN。

好比下面UPDATE語句,MySQL實際執行的是循環/嵌套子查詢(DEPENDENT SUBQUERY),其執行時間可想而知。

UPDATE operation o SET status = 'applying' WHERE o.id IN (SELECT id FROM (SELECT o.id, o.status FROM operation o WHERE o.group = 123 AND o.status NOT IN ( 'done' ) ORDER BY o.parent, o.id LIMIT 1) t); 

執行計劃:

+----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+ | 1 | PRIMARY | o | index | | PRIMARY | 8 | | 24 | Using where; Using temporary | | 2 | DEPENDENT SUBQUERY | | | | | | | | Impossible WHERE noticed after reading const tables | | 3 | DERIVED | o | ref | idx_2,idx_5 | idx_5 | 8 | const | 1 | Using where; Using filesort | +----+--------------------+-------+-------+---------------+---------+---------+-------+------+-----------------------------------------------------+ 

重寫爲JOIN以後,子查詢的選擇模式從DEPENDENT SUBQUERY變成DERIVED,執行速度大大加快,從7秒下降到2毫秒。

UPDATE operation o JOIN (SELECT o.id, o.status FROM operation o WHERE o.group = 123 AND o.status NOT IN ( 'done' ) ORDER BY o.parent, o.id LIMIT 1) t ON o.id = t.id SET status = 'applying' 

執行計劃簡化爲:

2.4 混合排序+----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+ | 1 | PRIMARY | | | | | | | | Impossible WHERE noticed after reading const tables | | 2 | DERIVED | o | ref | idx_2,idx_5 | idx_5 | 8 | const | 1 | Using where; Using filesort | +----+-------------+-------+------+---------------+-------+---------+-------+------+-----------------------------------------------------+

MySQL不能利用索引進行混合排序。但在某些場景,仍是有機會使用特殊方法提高性能的。

SELECT * FROM my_order o INNER JOIN my_appraise a ON a.orderid = o.id ORDER BY a.is_reply ASC, a.appraise_time DESC LIMIT 0, 20 

執行計劃顯示爲全表掃描:

+----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra +----+-------------+-------+--------+-------------+---------+---------+---------------+---------+-+ | 1 | SIMPLE | a | ALL | idx_orderid | NULL | NULL | NULL | 1967647 | Using filesort | | 1 | SIMPLE | o | eq_ref | PRIMARY | PRIMARY | 122 | a.orderid | 1 | NULL | +----+-------------+-------+--------+---------+---------+---------+-----------------+---------+-+ 

因爲is_reply只有0和1兩種狀態,咱們按照下面的方法重寫後,執行時間從1.58秒下降到2毫秒。

2.5  EXISTS語句SELECT * FROM ((SELECT * FROM my_order o INNER JOIN my_appraise a ON a.orderid = o.id AND is_reply = 0 ORDER BY appraise_time DESC LIMIT 0, 20) UNION ALL (SELECT * FROM my_order o INNER JOIN my_appraise a ON a.orderid = o.id AND is_reply = 1 ORDER BY appraise_time DESC LIMIT 0, 20)) t ORDER BY is_reply ASC, appraisetime DESC LIMIT 20; 

MySQL對待EXISTS子句時,仍然採用嵌套子查詢的執行方式。以下面的SQL語句:

SELECT * FROM my_neighbor n LEFT JOIN my_neighbor_apply sra ON n.id = sra.neighbor_id AND sra.user_id = 'xxx' WHERE n.topic_status < 4 AND EXISTS(SELECT 1 FROM message_info m WHERE n.id = m.neighbor_id AND m.inuser = 'xxx') AND n.topic_type <> 5 

執行計劃爲:

+----+--------------------+-------+------+-----+------------------------------------------+---------+-------+---------+ -----+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+ | 1 | PRIMARY | n | ALL | | NULL | NULL | NULL | 1086041 | Using where | | 1 | PRIMARY | sra | ref | | idx_user_id | 123 | const | 1 | Using where | | 2 | DEPENDENT SUBQUERY | m | ref | | idx_message_info | 122 | const | 1 | Using index condition; Using where | +----+--------------------+-------+------+ -----+------------------------------------------+---------+-------+---------+ -----+ 

去掉exists更改成join,可以避免嵌套子查詢,將執行時間從1.93秒下降爲1毫秒。

SELECT * FROM my_neighbor n INNER JOIN message_info m ON n.id = m.neighbor_id AND m.inuser = 'xxx' LEFT JOIN my_neighbor_apply sra ON n.id = sra.neighbor_id AND sra.user_id = 'xxx' WHERE n.topic_status < 4 AND n.topic_type <> 5 

新的執行計劃:

2.6 條件下推+----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+ | 1 | SIMPLE | m | ref | | idx_message_info | 122 | const | 1 | Using index condition | | 1 | SIMPLE | n | eq_ref | | PRIMARY | 122 | ighbor_id | 1 | Using where | | 1 | SIMPLE | sra | ref | | idx_user_id | 123 | const | 1 | Using where | +----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+

外部查詢條件不可以下推到複雜的視圖或子查詢的狀況有:

  1. 聚合子查詢;
  2. 含有LIMIT的子查詢;
  3. UNION 或UNION ALL子查詢;
  4. 輸出字段中的子查詢;

以下面的語句,從執行計劃能夠看出其條件做用於聚合子查詢以後:

SELECT * FROM (SELECT target, Count(*) FROM operation GROUP BY target) t WHERE target = 'rm-xxxx' 
+----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+ | 1 | PRIMARY | <derived2> | ref | <auto_key0> | <auto_key0> | 514 | const | 2 | Using where | | 2 | DERIVED | operation | index | idx_4 | idx_4 | 519 | NULL | 20 | Using index | +----+-------------+------------+-------+---------------+-------------+---------+-------+------+-------------+ 

肯定從語義上查詢條件能夠直接下推後,重寫以下:

SELECT target, Count(*) FROM operation WHERE target = 'rm-xxxx' GROUP BY target 

執行計劃變爲:

+----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+ | 1 | SIMPLE | operation | ref | idx_4 | idx_4 | 514 | const | 1 | Using where; Using index | +----+-------------+-----------+------+---------------+-------+---------+-------+------+--------------------+ 

關於MySQL外部條件不能下推的詳細解釋說明請參考之前文章:MySQL · 性能優化 · 條件下推到物化表

 2.7  提早縮小範圍

先上初始SQL語句:

SELECT * FROM my_order o LEFT JOIN my_userinfo u ON o.uid = u.uid LEFT JOIN my_productinfo p ON o.pid = p.pid WHERE ( o.display = 0 ) AND ( o.ostaus = 1 ) ORDER BY o.selltime DESC LIMIT 0, 15 

該SQL語句原意是:先作一系列的左鏈接,而後排序取前15條記錄。從執行計劃也能夠看出,最後一步估算排序記錄數爲90萬,時間消耗爲12秒。

+----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+ | 1 | SIMPLE | o | ALL | NULL | NULL | NULL | NULL | 909119 | Using where; Using temporary; Using filesort | | 1 | SIMPLE | u | eq_ref | PRIMARY | PRIMARY | 4 | o.uid | 1 | NULL | | 1 | SIMPLE | p | ALL | PRIMARY | NULL | NULL | NULL | 6 | Using where; Using join buffer (Block Nested Loop) | +----+-------------+-------+--------+---------------+---------+---------+-----------------+--------+----------------------------------------------------+ 

因爲最後WHERE條件以及排序均針對最左主表,所以能夠先對my_order排序提早縮小數據量再作左鏈接。SQL重寫後以下,執行時間縮小爲1毫秒左右。

SELECT * FROM ( SELECT * FROM my_order o WHERE ( o.display = 0 ) AND ( o.ostaus = 1 ) ORDER BY o.selltime DESC LIMIT 0, 15 ) o LEFT JOIN my_userinfo u ON o.uid = u.uid LEFT JOIN my_productinfo p ON o.pid = p.pid ORDER BY o.selltime DESC limit 0, 15 

再檢查執行計劃:子查詢物化後(select_type=DERIVED)參與JOIN。雖然估算行掃描仍然爲90萬,可是利用了索引以及LIMIT 子句後,實際執行時間變得很小。

2.8  中間結果集下推+----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+ | 1 | PRIMARY | <derived2> | ALL | NULL | NULL | NULL | NULL | 15 | Using temporary; Using filesort | | 1 | PRIMARY | u | eq_ref | PRIMARY | PRIMARY | 4 | o.uid | 1 | NULL | | 1 | PRIMARY | p | ALL | PRIMARY | NULL | NULL | NULL | 6 | Using where; Using join buffer (Block Nested Loop) | | 2 | DERIVED | o | index | NULL | idx_1 | 5 | NULL | 909112 | Using where | +----+-------------+------------+--------+---------------+---------+---------+-------+--------+----------------------------------------------------+

再來看下面這個已經初步優化過的例子(左鏈接中的主表優先做用查詢條件):

SELECT a.*, c.allocated FROM ( SELECT resourceid FROM my_distribute d WHERE isdelete = 0 AND cusmanagercode = '1234567' ORDER BY salecode limit 20) a LEFT JOIN ( SELECT resourcesid, sum(ifnull(allocation, 0) * 12345) allocated FROM my_resources GROUP BY resourcesid) c ON a.resourceid = c.resourcesid 

那麼該語句還存在其它問題嗎?不難看出子查詢 c 是全表聚合查詢,在表數量特別大的狀況下會致使整個語句的性能降低。

其實對於子查詢 c,左鏈接最後結果集只關心能和主表resourceid能匹配的數據。所以咱們能夠重寫語句以下,執行時間從原來的2秒降低到2毫秒。

SELECT a.*, c.allocated FROM ( SELECT resourceid FROM my_distribute d WHERE isdelete = 0 AND cusmanagercode = '1234567' ORDER BY salecode limit 20) a LEFT JOIN ( SELECT resourcesid, sum(ifnull(allocation, 0) * 12345) allocated FROM my_resources r, ( SELECT resourceid FROM my_distribute d WHERE isdelete = 0 AND cusmanagercode = '1234567' ORDER BY salecode limit 20) a WHERE r.resourcesid = a.resourcesid GROUP BY resourcesid) c ON a.resourceid = c.resourcesid 

可是子查詢 a 在咱們的SQL語句中出現了屢次。這種寫法不只存在額外的開銷,還使得整個語句顯的繁雜。使用WITH語句再次重寫:

WITH a AS ( SELECT resourceid FROM my_distribute d WHERE isdelete = 0 AND cusmanagercode = '1234567' ORDER BY salecode limit 20) SELECT a.*, c.allocated FROM a LEFT JOIN ( SELECT resourcesid, sum(ifnull(allocation, 0) * 12345) allocated FROM my_resources r, a WHERE r.resourcesid = a.resourcesid GROUP BY resourcesid) c ON a.resourceid = c.resourcesid 

AliSQL即將推出WITH語法,敬請期待。

總結

  1. 數據庫編譯器產生執行計劃,決定着SQL的實際執行方式。可是編譯器只是盡力服務,全部數據庫的編譯器都不是盡善盡美的。上述提到的多數場景,在其它數據庫中也存在性能問題。瞭解數據庫編譯器的特性,才能避規其短處,寫出高性能的SQL語句。
  2. 程序員在設計數據模型以及編寫SQL語句時,要把算法的思想或意識帶進來。
  3. 編寫複雜SQL語句要養成使用WITH語句的習慣。簡潔且思路清晰的SQL語句也能減少數據庫的負擔 ^^。

2、選擇合適的數據類型

(1)使用可存下數據的最小的數據類型,整型 < date,time < char,varchar < blob
(2)使用簡單的數據類型,整型比字符處理開銷更小,由於字符串的比較更復雜。如,int類型存儲時間類型,bigint類型轉ip函數
(3)使用合理的字段屬性長度,固定長度的表會更快。使用enum、char而不是varchar
(4)儘量使用not null定義字段
(5)儘可能少用text,非用不可最好分表

3、選擇合適的索引列

(1)查詢頻繁的列,在where,group by,order by,on從句中出現的列
(2)where條件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出現的列
(3)長度小的列,索引字段越小越好,由於數據庫的存儲單位是頁,一頁中能存下的數據越多越好
(4)離散度大(不一樣的值多)的列,放在聯合索引前面。查看離散度,經過統計不一樣的列值來實現,count越大,離散程度越高:

mysql> SELECT COUNT(DISTINCT column_name) FROM table_name;

4、使用命令分析

(1)SHOW查看狀態
1.顯示狀態信息

mysql> SHOW [SESSION|GLOBAL] STATUS LIKE '%Status_name%';

session(默認):取出當前窗口的執行
global:從mysql啓動到如今
(a)查看查詢次數(插入次數com_insert、修改次數com_insert、刪除次數com_delete)

mysql> SHOW STATUS LIKE 'com_select';

(b)查看鏈接數(登陸次數)

mysql> SHOW STATUS LIKE 'connections';

(c)數據庫運行時間

mysql> SHOW STATUS LIKE 'uptime';

(d)查看慢查詢次數

mysql> SHOW STATUS LIKE 'slow_queries';

(e)查看索引使用的狀況:

mysql> SHOW STATUS LIKE 'handler_read%';

handler_read_key:這個值越高越好,越高表示使用索引查詢到的次數。
handler_read_rnd_next:這個值越高,說明查詢低效。
2.顯示系統變量

mysql> SHOW VARIABLES LIKE '%Variables_name%';

3.顯示InnoDB存儲引擎的狀態

mysql> SHOW ENGINE INNODB STATUS;

(2)EXPLAIN分析查詢

mysql> EXPLAIN SELECT column_name FROM table_name;

explain查詢sql執行計劃,各列含義:
table:表名;
type:鏈接的類型
    -const:主鍵、索引;
    -eq_reg:主鍵、索引的範圍查找;
    -ref:鏈接的查找(join)
    -range:索引的範圍查找;
    -index:索引的掃描;
    -all:全表掃描;
possible_keys:可能用到的索引;
key:實際使用的索引;
key_len:索引的長度,越短越好;
ref:索引的哪一列被使用了,常數較好;
rows:mysql認爲必須檢查的用來返回請求數據的行數;
extra:using filesort、using temporary(常出如今使用order by時)時須要優化。
    -Using filesort  額外排序。看到這個的時候,查詢就須要優化了
    -Using temporary 使用了臨時表。看到這個的時候,也須要優化
(3)PROFILING分析SQL語句
1.開啓profile。查看當前SQL執行時間

mysql> SET PROFILING=ON; 
mysql> SHOW profiles;

2.查看全部用戶的當前鏈接。包括執行狀態、是否鎖表等

mysql> SHOW processlist;

(4)PROCEDURE ANALYSE()取得建議
經過分析select查詢結果對現有的表的每一列給出優化的建議

mysql> SELECT column_name FROM table_name PROCEDURE ANALYSE();

(5)OPTIMIZE TABLE回收閒置的數據庫空間

mysql> OPTIMIZE TABLE table_name;

對於MyISAM表,當表上的數據行被刪除時,所佔據的磁盤空間並無當即被回收,使用命令後這些空間將被回收,而且對磁盤上的數據行進行重排(注意:是磁盤上,而非數據庫)。
對於InnoDB表,OPTIMIZE TABLE被映射到ALTER TABLE上,這會重建表。重建操做能更新索引統計數據並釋放成簇索引中的未使用的空間。
只需在批量刪除數據行以後,或按期(每週一次或每個月一次)進行一次數據表優化操做便可,只對那些特定的表運行。
(6)REPAIR TABLE修復被破壞的表

mysql> REPAIR TABLE table_name;

(7)CHECK TABLE檢查表是否有錯誤

mysql> CHECK TABLE table_name;

5、定位慢查詢

MySQL慢查詢

6、分區

MySQL分區和分表

7、配置優化

MySQL配置優化

 

8、優化基本原則

1.   SQL基礎原則

  1)儘可能不在數據庫作運算

  2)控制單表數據量 純INT不超過10M條,含Char不超過5M條

  3)保持表身段苗條

  4)平衡範式和冗餘

  5)拒絕大SQL,復瑣事務,大批量任務

2.   字段類原則

  1)用好數值字段,儘可能簡化字段位數

  2)把字符轉化爲數字

  3)優先使用Enum或Set

  4)避免使用Null字段

  5)少用並拆封Text/Blob

  6)不在數據庫中存圖片

3.   索引類原則

  1)謹慎合理添加索引

  2)字符字段必須創建前綴索引

  3)不在索引列作運算

  4)自增列或全局ID作InnoDB主鍵

  5)儘可能不用外鍵

4.    SQL語句類

  1)SQL儘量簡單

  2)保持事務鏈接短小

  3)儘量避免使用SP/Trigger/Function

  4)儘可能不用Select *

  5)改寫Or爲IN()

  6)改寫Or爲Union

  7)避免負向查詢和%前綴模糊查詢

  8)Count不要使用在可Null的字段上面

  9)減小Count(*)

  10)Limit高效分頁,SELECT * FROM message WHERE id > 9527 (or sub select) limit 10

  11)使用Union ALL 而不用Union

  12)分解連接,保證高併發

  13)Group By 去除排序

  14)同數據類型的列值比較

  15)Load Data導入數據,比Insert快20倍

  16)打散大批量更新,儘可能凌晨操做

5.   約定類原則

  1)隔離線上線下

  2)禁止未經DBA認證的子查詢

  3)永遠不在程序段顯式加鎖

  4)表字符集統一使用UTF8MB4

相關文章
相關標籤/搜索