一、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優化
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 | +----+-------------+-------+--------+ -----+------------------------------------------+---------+ -----+------+ -----+
外部查詢條件不可以下推到複雜的視圖或子查詢的狀況有:
以下面的語句,從執行計劃能夠看出其條件做用於聚合子查詢以後:
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語法,敬請期待。
總結
2、選擇合適的數據類型
(1)使用可存下數據的最小的數據類型,整型 < date,time < char,varchar < blob
(2)使用簡單的數據類型,整型比字符處理開銷更小,由於字符串的比較更復雜。如,int類型存儲時間類型,bigint類型轉ip函數
(3)使用合理的字段屬性長度,固定長度的表會更快。使用enum、char而不是varchar
(4)儘量使用not null定義字段
(5)儘可能少用text,非用不可最好分表
(1)查詢頻繁的列,在where,group by,order by,on從句中出現的列
(2)where條件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出現的列
(3)長度小的列,索引字段越小越好,由於數據庫的存儲單位是頁,一頁中能存下的數據越多越好
(4)離散度大(不一樣的值多)的列,放在聯合索引前面。查看離散度,經過統計不一樣的列值來實現,count越大,離散程度越高:
mysql> SELECT COUNT(DISTINCT column_name) FROM table_name;
(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;
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