高性能mysql 4,5,6章優化總結
針對數據庫的優化,我們不能單純的說從哪一個方面,需要結合數據表的建立,數據類型的選擇,索引的設計和sql語句來考慮,我就針對怎麽建表,怎麽選擇數據類型,如何應用B-tree索引,hash索引和覆蓋索引的特點來建立高效的索引策略,然後我具體對 count()查詢,最大最小值查詢,關聯查詢,子查詢,GROUP BY ,limit 分頁,Union查詢做一些具體的說明,最後我說一下怎樣使用切分查詢和分解關聯查詢來重構我們的查詢方式,
一、數據表的設計
首先我們要根據範式化和反範式化各自的優缺點,選擇一個最佳的設計。
(反範式化設計,一個典型列子就是緩存表,它的特點就是在不同的表中存儲相同的列,它很好的避免了關聯
二、數據類型的選擇:
我主要說3點
1、選擇簡單的,不超過範圍的最小類型,避免使用 NULL(可為NULL的列使得索引,索引統計和值比較都更復雜,還會使用更多的存儲空間,在mysql裏也需要特殊處理)
Example:
(1)日期時間類型最好使用 timestamp 而不用datetime存儲,datetime使用了8個字節的存儲空間,而timestamp 只使用4個字節的存儲空間,可以使用 FROM_UNIXTIME()
(2)IP地址是一個 32 位無符號整數,應該 unsigned int 來存儲,不應該使用 varchar(15) 來存儲,可以使用 INET_NTOA() 和INET_ATON() 兩個函數來相互轉化。
2、註意char 和 varchar 的區別,(varchar適合存儲最大長度比平均長度大很多和列的更新少,不容易產生碎片的數據,而char適合存儲短的,所有值接近同一個長度的數據,對於經常變更的數據也適合用char存儲)
3、對於相似或相關的值盡量使用同種數據類型存儲,特別是在關聯條件中使用的列。
三、高效的索引策略
MyISAM,InnoDB和
1、首先我說說 hash 索引,因為hash查找非常快,hash索引是基於哈希表實現,存儲引擎會對所有的索引列計算一個哈希碼,哈希索引將所有的哈希碼存儲在索引中,同時在哈希表中保存每個指向數據行的指針。我們就可以利用它的這個特點來創建一個自定義hash索引提高查詢效率。
舉個列子,假如現在有一個address表,裏面存儲大量的 url,而且需要根據url進行搜素查找,我需要執行一條這樣的查詢:
SELECT id FROM address WHERE url=’http://www.mysql.com’;
那麽我直接在url上面建立一個索引好不好呢,答案是肯定能提高查詢效率,但是由於這個字段太長,直接用它做索引不是最佳選擇,我們應該刪除url上的索引,再數據表中增加一個字段crc_url,使用CRC32做hash,然後執行下面的查詢:
SELECT id FROM address WHERE url=’http://www.mysql.com’ AND url_crc = CRC32(“http://www.mysql.com”);
這樣做性能會非常高,因為mysql優化器會使用這個選擇性更高而且體積更小的基於url_crc列的索引來完成查找,只需要根據哈希值做快速的整數比較就可找到索引條目,然後返回對應的列,哈希值我們可以用觸發器來維護,
CREATE TRIGGER address_hash_crc_ins BEFORE INSERT ON address FOR EACH ROW BEGIN SET NEW.crc_url = crc32(NEW.url); CREATE TRIGGER address_hash_crc_upd BEFORE UPDATE ON address FOR EACH ROW BEGIN SET NEW.crc_url = crc32(NEW.url);
當然InnoDB有一個特殊的功能叫“自適應哈希索引,當InnoDB註意到某些索引使用的非常頻繁是就會在內存中基於B-tree索引在創建一個哈希索引,讓B-tree索引頁具有hash索引的一些優點”。
當然雖然哈hash索引查找速度非常快。但是也有它的局限性由於它不是按照索引值順序存儲的,所以它無法用於排序操作,也不支持部分索引列匹配查找,因為hash索引始終是使用索引列的全部內容來計算hash值的,哈希索引只支持等值比較查詢。
2、在說一下InnoDB支持的聚簇索引,聚簇索引的數據實際上是保存在索引的葉子頁中,我先說說MyISAM和InnoDB的數據存儲結構:
舉個列子:
假如現在有一個這樣的查詢:
SELECT * FROM questions WHERE question_userid=1 AND question_title LIKE ‘%PHP試題%’;
當數據量很大的時候,返回的行有比較少時,查詢速度回很慢,我們需要重寫查詢並巧妙的設計索引,我需要先添加一個多列索引 userid_title(question_userid,question_title),我將查詢改成這樣:
SELECT * FROM questions JOIN (SELECT question_id FROM questions WHERE question_userid=1 AND question_title LIKE ‘%PHP試題%’) AS t1 ON(t1.question_id = questions.question_id);
使用這種延遲關聯的查詢方式,查詢的第一階段mysql可以使用覆蓋索引,然後根據這些question_id值在外層查詢匹配獲取需要的所有值。
四、創建高效的sql語句,
1、優化count
如果是MyISAM存儲引擎,而且沒有任何的where條件,我們直接使用count(*),mysql會利用存儲引擎的特點直接獲取這個值,還有一個方法就是利用反正特性,比如有這樣一個查詢:
SELECT COUNT(*) FROM questions WHERE id>5;
此時將語句改寫成:
SELECT (SELECT COUNT(*) FROM questions) - COUNT(*) FROM questions WHERE id<5;
這樣就可以大大減小需要掃描的行數,查詢優化器會直接將子查詢當做一個常數來處理,而且查詢小於5的數據很少很多,如果只是需要一個粗略值的話可以直接使用EXPLAIN 中優化器估算出的一個行數就可以當做這個近似值。如果需要分別查詢單選題題,判斷題各有多少道,可以這樣寫:
SELECT COUNT(question_type=1 OR NULL) AS ‘單選題’, COUNT(question_type=2 OR NULL) AS ‘多選題’ FROM qustions;
2、優化min()
SELECT MIN(question_id) FROM questions WHERE question_userid=1;
改寫成:
SELECT question_id FROM questions USE INDEX(PRIMARY) WHERE question_userid=1 LIMIT 1;
這裏使用了一個優化器提示 USE INDEX() 來告訴優化器使用主鍵索引來查詢記錄,由於數據存儲的時候是按照主鍵值從小到大存儲的,那麽滿足條件的第一條記錄一定是最小值。
3、優化關聯查詢
如果 A 與 B 通過 c 列關聯,關聯順序是 B,A,則只需要在A表的c列上建立索引就好,如果在B表上也建索引,那麽這種冗余的索引只會帶來額外的負擔,還要註意它們的關聯字段的數據類型盡量用通一種數據類型,如果查詢語句中有 ORDER BY 或者 GROUP BY,那麽最好只涉及到一個表中的列,這樣mysql才有可能使用索引來優化這個過程。
4、優化GROUP BY
當我們需用分組做分組排序的時候,這個時候我們創建索引就應該盡量讓它滿足既能用於查找行又能用於排序操作,這樣就不用創建臨時表來做額外的排序操作了,那麽優化GROUP BY 最好的方法就是使用松散索引掃描,如果不能滿足松散索引掃描的話,也盡量使用緊湊索引掃描,避免使用臨時表來排序。
(1)使用松散索引掃描(當mysql完全利用索引掃描來實現GROUP BY的時候,並不需要掃描所有滿足條件的索引鍵就可以完成操作的出結果)
假設有一個表 t1 有4個列 (c1,c2,c3,c4), 給它建立了一個多列索引 (c2,c3,c4);
做類似這樣查詢 SELECT c2,MAX(c3) FROM t1 GROUP BY c2,c3;
例子:
SELECT question_userid,question_type FROM questions GROUP BY question_userid,question_type;
(2)使用緊湊索引掃描:
SELECT c2,MAX(c3) FROM t1 WHERE c2=const GROUP BY c3,c4; SELECT question_userid,question_type FROM questions WHERE question_userid=1 GROUP BY question_type;
緊湊索引掃描和松散索引掃描的區別在於,緊湊索引掃描需要在掃描索引的時候,讀取所有滿足條件的索引鍵,然後在根據讀取的數據來完成GROUP BY 操作,它需要訪問WHERE條件中所限定的所有索引鍵之後才能得出結果。這裏的GROUP BY並不是一個連續的索引,但是WHERE 條件中的question_userid 是個常數,彌補了缺失的索引,因此使用緊湊索引掃描。
(3)使用臨時表
如果這兩種中條件都打不到的話,就只能使用臨時表來進行排序了,如果數據量小可以直接在內存裏進行,數據量大的話可能還需要借助磁盤來完成,如果我們自己清楚臨時表的數據量大小,可以使用優化器提示SQL_SMALL_RESULT 或SQL_GIG_RESULT 告訴優化器在哪兒排序。如果我們只需要對結果分組而不需要排序,可以加一句 ORDER BY NULL 來避免GROUP BY 默認的按照分組字段進行排序。
5、優化 UNION
除非確實需要去除重復的行,否則一定要使用 UNION ALL , 因為如果沒有ALL,mysql會給臨時表加上DISTINCT做唯一性檢查,這樣做代價非常高,
Mysql在做UNION 查詢時,無法將限制條件從外層“下推”到內層,例如:
(SELECT c1,c2 FROM t1 ORDER BY c2) UNION ALL (SELECT c3,c4 FROM t2 ORDER BY c4) LIMIT 20;
假設從t1表中查出了500條記錄,從t2表中查出了800 條記錄,那麽mysql會將這1300 條記錄放入臨時表在取出20條。因為它無法將limit下推到內層,我們可以這樣優化一下:
(SELECT c1,c2 FROM t1 ORDER BY c2 LIMIT 20) UNION ALL (SELECT c3,c4 FROM t2 ORDER BY c4 LIMIT 20) LIMIT 20;
這樣臨時表就只有40條記錄了,再取出20條。
6、優化limit 分頁
對於limit的優化,最常用的地方就是分頁,假設我們有很多頁,有例如 limit 1000,20 這樣的查詢,那麽mysql需要查詢1020條記錄,然後將前面的1000條都扔掉,然後返回最後20條,這樣做代價太高,我們可以這樣來優化: 盡可能使用索引覆蓋掃描,而不是查詢所有的列,然後根據需要做一次關聯在返回所需的列:
例如:
SELECT question_name,question_type FROM questions ORDER BY question_inserttime LIMIT 1000,20;
可以這樣改:
SELECT question_name,question_type INNOR JOIN (SELECT question_id FROM questions ORDER BY question_inserttime LIMIT 1000,20) AS t1 USEING(question_id);
這裏使用了延遲關聯,先使用索引覆蓋掃描快速找到對應的20個question_id,然後做一次關聯操作返回所需的列。
7、子查詢
對於子查詢最好是使用關聯查詢來代替。
當然除了這些查詢語句優化外,還可以重構查詢方式,比如切分查詢和分解關聯查詢;
五、重構查詢方式
1、切分查詢
所謂的切分查詢,就是將一個大查詢分而治之,將一個大查詢切分成小查詢,每個查詢的功能完全一樣,只完成一小部分,每次只完成一小部分查詢結果。
例如我們有一個很大的message表,我們需要定期去刪除裏面的數據,需要執行這樣一條sql:
DELETE FROM message WHERE create_time < DATE_SUB(NOW(),INTVAL 1 MONTH);
我們可以將它改成這樣:
$row_effect = 0; do { $sql = “DELETE FROM message WHERE create_time<DATE_SUB(NOW(),INTVAL 1 MONTH) LIMIT 10000”; $row_effect = $db -> execte($sql); }while $row_effect >0
2、分解關聯查詢
分解關聯查詢就是將多表關聯查詢切分成單個查詢,這樣做可以讓緩存命中率更高,而且執行單個查詢可以減少鎖的競爭。
該文章是我學習了高性能mysql一書後自己做的一個總結,如果有轉載請註明出處:http://www.cnblogs.com/chrdai/p/6809369.html
高性能mysql 4,5,6章優化總結