-- 優化SQL的一般步驟 -- 一、通過show status和應用特點了解各種SQL的執行頻率 /* 通過SHOW STATUS可以提供服務器狀態信息,也可以使用mysqladmin extended-status命令獲得。SHOW STATUS可以根據需要顯示session級別的統計結果和global級別的統計結果。 以下幾個參數對Myisam和Innodb存儲引擎都計數: 1.Com_select 執行select操作的次數,一次查詢只累加1; 2.Com_insert 執行insert操作的次數,對於批量插入的insert操作,只累加一次; 3.Com_update 執行update操作的次數; 4.Com_delete 執行delete操作的次數; */ SHOW STATUS WHERE Variable_name = 'Com_select'; /* 以下幾個參數是針對Innodb存儲引擎計數的,累加的算法也略有不同: 1.Innodb_rows_read select查詢返回的行數; 2.Innodb_rows_inserted 執行Insert操作插入的行數; 3.Innodb_rows_updated 執行update操作更新的行數; 4.Innodb_rows_deleted 執行delete操作刪除的行數; 通過以上幾個參數,可以很容易的了解當前數據庫的應用是以插入更新為主還是以查詢操作為主,以及各種類型的SQL大致的執行比例是多少。 對於更新操作的計數,是對執行次數的計數,不論提交還是回滾都會累加。 對於事務型的應用,通過 Com_commit 和 Com_rollback 可以了解事務提交和回滾的情況,對於回滾操作非常頻繁的數據庫,可能意味着應用編寫存在問題。 此外,以下幾個參數便於我們了解數據庫的基本情況: 1.Connections 試圖連接Mysql服務器的次數 2.Uptime 服務器工作時間 3.Slow_queries 慢查詢的次數 二、定位執行效率較低的SQL語句 可以通過以下兩種方式定位執行效率較低的SQL語句: 1.可以通過慢查詢日志定位那些執行效率較低的sql語句,用--log-slow-queries[=file_name]選項啟動時, mysqld寫一個包含所有執行時間超過long_query_time秒的SQL語句的日志文件。可以鏈接到管理維護中的相關章節。 2.慢查詢日志在查詢結束以后才紀錄,所以在應用反映執行效率出現問題的時候查詢慢查詢日志並不能定位問題, 可以使用show processlist命令查看當前MySQL在進行的線程,包括線程的狀態,是否鎖表等等,可以實時的查看SQL執行情況,同時對一些鎖表操作進行優化。 */ SHOW PROCESSLIST; /* 三、通過EXPLAIN分析低效SQL的執行計划 通過以上步驟查詢到效率低的SQL后,我們可以通過explain或者desc 獲取MySQL如何執行SELECT語句的信息,包括select語句執行過程表如何連接和連接的次序。 explain 可以知道什么時候必須為表加入索引以得到一個使用索引來尋找記錄的更快的SELECT。 */ EXPLAIN SELECT * FROM message a LEFT JOIN mytable b ON a.id = b.id WHERE a.id=1; /* 返回結果 +--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+ | id | select_type | table | type | possible_keys| key | key_len | ref | rows | Extra | +--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+ | 1 | SIMPLE | a | const | PRIMARY | PRIMARY | 4 | const | 1 | | | 1 | SIMPLE | b | ALL | NULL | NULL | NULL | | 9999 | | +--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+ select_type:select 類型 table: 輸出結果集的表 type: 表示表的連接類型 ①當表中僅有一行是type的值為system是最佳的連接類型; ②當select操作中使用索引進行表連接時type的值為ref; ③當select的表連接沒有使用索引時,經常會看到type的值為ALL,表示對該表進行了全表掃描,這時需要考慮通過創建索引來提高表連接的效率。 possible_keys:表示查詢時,可以使用的索引列. key: 表示使用的索引 key_len: 索引長度 rows: 掃描范圍 Extra:執行情況的說明和描述 四、確定問題,並采取相應的優化措施 經過以上步驟,基本可以確認問題出現的原因,可以根據情況采取相應的措施,進行優化提高執行的效率。 例如上面的例子,我們確認是對b表的全表掃描導致效率的不理想,我們對b表的 id 字段創建了索引,查詢需要掃描的行數明顯較少。 返回結果 +--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+ | id | select_type | table | type | possible_keys| key | key_len | ref | rows | Extra | +--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+ | 1 | SIMPLE | a | const | PRIMARY | PRIMARY | 4 | const | 1 | | | 1 | SIMPLE | b | const | PRIMARY | PRIMARY | 4 | const | 1 | | +--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+ 大批量插入數據時優化SQL語句============================================== 一、對於Myisam類型的表,可以通過以下步驟快速的導入大量的數據。 ALTER TABLE tablename DISABLE KEYS; 批量插入數據 ALTER TABLE tablename ENABLE KEYS; 前后兩個命令用來打開或者關閉Myisam表非唯一索引的更新。在導入大量的數據到一個非空的Myisam表時,通過設置這兩個命令,可以提高導入的效率。 對於導入大量數據到一個空的Myisam表,默認就是先導入數據然后才創建索引的,所以不用進行設置。 */ ALTER TABLE mytable DISABLE KEYS; INSERT INTO mytable(id, username, city, age) VALUES(1, 'name1', 'city1', 10),(2, 'name2', 'city2', 20),(3, 'name3', 'city3', 30); ALTER TABLE mytable ENABLE KEYS; /* 二、而對於Innodb類型的表,這種方式並不能提高導入數據的效率。對於Innodb類型的表,我們有以下幾種方式可以提高導入的效率: ①因為Innodb類型的表是按照主鍵的順序保存的,所以將導入的數據按照主鍵的順序排列,可以有效的提高導入數據的效率。 如果Innodb表沒有主鍵,那么系統會默認創建一個內部列作為主鍵,所以如果可以給表創建一個主鍵,將可以利用這個優勢提高導入數據的效率。 ②在導入數據前執行SET UNIQUE_CHECKS=0,關閉唯一性校驗,在導入結束后執行SET UNIQUE_CHECKS=1,恢復唯一性校驗,可以提高導入的效率。 ③如果應用使用自動提交的方式,建議在導入前執行SET AUTOCOMMIT=0,關閉自動提交,導入結束后再執行SET AUTOCOMMIT=1,打開自動提交,也可以提高導入的效率。 */ SET UNIQUE_CHECKS=0; SET UNIQUE_CHECKS=1; SET AUTOCOMMIT=0; SET AUTOCOMMIT=1; /* 優化insert語句============================================== 1、如果同時插入很多行,請使用多個值的INSERT語句。這比使用分開INSERT語句快(在一些情況中幾倍)。 Insert into test values(1,2),(1,3),(1,4)… 2、如果從不同客戶插入很多行,能通過使用INSERT DELAYED 語句得到更高的速度。 Delayed 的含義是讓insert 語句馬上執行,其實數據都被放在內存的隊列中,並沒有真正寫入磁盤;這比每條語句分別插入要快的多; LOW_PRIORITY 剛好相反,在所有其他用戶對表的讀寫完后才進行插入; 3、將索引文件和數據文件分在不同的磁盤上存放(利用建表中的選項); 4、如果批量插入,可以增加bulk_insert_buffer_size變量值的方法來提高速度,但是,這只能對myisam表使用; 5、當從一個文本文件裝載一個表時,使用LOAD DATA INFILE。這通常比使用很多INSERT語句快20倍; 6、根據應用情況使用 replace 語句代替 insert; 7、根據應用情況使用 ignore 關鍵字忽略重復記錄。 */ INSERT DELAYED INTO mytable(id, username, city, age) VALUES(4, 'name4', 'city4', 40); INSERT LOW_PRIORITY INTO mytable(id, username, city, age) VALUES(5, 'name5', 'city5', 50); REPLACE INTO mytable(id, username, city, age) VALUES(5, 'name5', 'city5', 50); INSERT IGNORE INTO mytable(id, username, city, age) VALUES(5, 'name5', 'city5', 50); /* 優化group by語句============================================== 默認情況下,MySQL排序所有GROUP BY col1,col2,....。查詢的方法如同在查詢中指定ORDER BY col1,col2,...。 如果顯式包括一個包含相同的列的ORDER BY子句,MySQL可以毫不減速地對它進行優化,盡管仍然進行排序。 如果查詢包括GROUP BY但你想要避免排序結果的消耗,你可以指定 ORDER BY NULL禁止排序。例如: */ SELECT * FROM mytable GROUP BY username ORDER BY NULL; /* 優化order by語句============================================== 在某些情況中,MySQL可以使用一個索引在 ORDER BY 子句中,而不需要額外的排序。 where條件和order by使用相同的索引,並且order by的順序和索引順序相同,並且order by的字段都是升序或者都是降序。 例如:下列sql可以使用索引。 SELECT * FROM t1 ORDER BY key_part1,key_part2,... ; -- 同一組合索引的某一部分 SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, key_part2 DESC; SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC; 但是以下情況不使用索引: SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC; -- order by的字段混合ASC和DESC SELECT * FROM t1 WHERE key2=constant ORDER BY key1; -- 用於查詢行的關鍵字與ORDER BY中所使用的不相同 SELECT * FROM t1 ORDER BY key1, key2; -- 對不同的索引使用ORDER BY: 優化join語句============================================== Mysql4.1開始支持SQL的子查詢。這個技術可以使用SELECT語句來創建一個單列的查詢結果,然后把這個結果作為過濾條件用在另一個查詢中。 使用子查詢可以一次性的完成很多邏輯上需要多個步驟才能完成的SQL操作,同時也可以避免事務或者表鎖死,並且寫起來也很容易。 但是,有些情況下,子查詢可以被更有效率的連接(JOIN).. 替代。 假設我們要將所有沒有訂單記錄的用戶取出來,可以用下面這個查詢完成: SELECT * FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FROM salesinfo ) 如果使用連接(JOIN).. 來完成這個查詢工作,速度將會快很多。尤其是當salesinfo表中對CustomerID建有索引的話,性能將會更好,查詢如下: SELECT * FROM customerinfo LEFT JOIN salesinfoON customerinfo.CustomerID=salesinfo.CustomerID WHERE salesinfo.CustomerID IS NULL 連接(JOIN).. 之所以更有效率一些,是因為 MySQL不需要在內存中創建臨時表來完成這個邏輯上的需要兩個步驟的查詢工作。 insert、update、delete的使用順序============================================== MySQL還允許改變語句調度的優先級,它可以使來自多個客戶端的查詢更好地協作,這樣單個客戶端就不會由於鎖定而等待很長時間。 改變優先級還可以確保特定類型的查詢被處理得更快。 我們首先應該確定應用的類型,判斷應用是以查詢為主還是以更新為主的,是確保查詢效率還是確保更新的效率,決定是查詢優先還是更新優先。 下面我們提到的改變調度策略的方法主要是針對Myisam存儲引擎的,對於Innodb存儲引擎,語句的執行是由獲得行鎖的順序決定的。 MySQL的默認的調度策略可用總結如下: 1.寫入操作優先於讀取操作。 2.對某張數據表的寫入操作某一時刻只能發生一次,寫入請求按照它們到達的次序來處理。 3.對某張數據表的多個讀取操作可以同時地進行。 MySQL提供了幾個語句調節符,允許你修改它的調度策略: 1.LOW_PRIORITY 關鍵字應用於 DELETE 、 INSERT 、 LOAD DATA 、 REPLACE和UPDATE 。 2.HIGH_PRIORITY關鍵字應用於SELECT和INSERT語句。 3.DELAYED關鍵字應用於INSERT和REPLACE語句。 如果寫入操作是一個LOW_PRIORITY(低優先級)請求,那么系統就不會認為它的優先級高於讀取操作。 在這種情況下,如果寫入者在等待的時候,第二個讀取者到達了,那么就允許第二個讀取者插到寫入者之前。 只有在沒有其它的讀取者的時候,才允許寫入者開始操作。這種調度修改可能存在LOW_PRIORITY寫入操作永遠被阻塞的情況。 SELECT查詢的HIGH_PRIORITY(高優先級)關鍵字也類似。它允許SELECT插入正在等待的寫入操作之前,即使在正常情況下寫入操作的優先級更高。 另外一種影響是,高優先級的SELECT在正常的SELECT語句之前執行,因為這些語句會被寫入操作阻塞。 如果你希望所有支持LOW_PRIORITY選項的語句都默認地按照低優先級來處理,那么請使用--low-priority-updates選項來啟動服務器。 通過使用INSERT HIGH_PRIORITY來把INSERT語句提高到正常的寫入優先級,可以消除該選項對單個INSERT語句的影響。 */ INSERT LOW_PRIORITY INTO mytable(id, username, city, age) VALUES(7, 'name7', 'city7', 70); /* 優化數據表============================================== 一、優化表的數據類型 表需要使用何種數據類型,是需要根據應用來判斷的。 雖然應用設計的時候需要考慮字段的長度留有一定的冗余,但是不推薦讓很多字段都留有大量的冗余,這樣即浪費存儲也浪費內存。 我們可以使用PROCEDURE ANALYSE()對當前已有應用的表類型的判斷,該函數可以對數據表中的列的數據類型提出優化建議,可以根據應用的實際情況酌情考慮是否實施優化。 語法: SELECT * FROM tbl_name PROCEDURE ANALYSE(); SELECT * FROM tbl_name PROCEDURE ANALYSE(16,256); 輸出的每一列信息都會對數據表中的列的數據類型提出優化建議。第二個例子告訴PROCEDURE ANALYSE()不要為那些包含的值多於16個或者256字節的ENUM類型提出建議。 如果沒有這樣的限制,輸出信息可能很長;ENUM定義通常很難閱讀。 在對字段類型進行優化時,可以根據統計信息並結合應用的實際情況對其進行優化。 二、通過拆分,提高表的訪問效率 這里我們所說的拆分,主要是針對Myisam類型的表,拆分的方法可以分成兩種情況: 1、縱向拆分: 縱向拆分是只按照應用訪問的頻度,將表中經常訪問的字段和不經常訪問的字段拆分成兩個表,經常訪問的字段盡量是定長的,這樣可以有效的提高表的查詢和更新的效率。 2、橫向拆分: 橫向拆分是指按照應用的情況,有目的的將數據橫向拆分成幾個表或者通過分區分到多個分區中,這樣可以有效的避免Myisam表的讀取和更新導致的鎖問題。 三、逆規范化 數據庫的規范化設計強調數據的獨立性,數據應該盡可能少地冗余,因為存在過多的冗余數據,這就意味着要占用了更多的物理空間,同時也對數據的維護和一致性檢查帶來了問題。 對於查詢操作很多的應用,一次查詢可能需要訪問多表進行,如果通過冗余相同數據紀錄在一個表中,更新的代價增加不多, 但是查詢操作效率可以有明顯提高,這種情況就可以考慮通過冗余數據來提高效率。 四、使用冗余統計表 使用create temporary table語法,它是基於session的表,表的數據保存在內存里面,當session斷掉后,表自然消除。 對於大表的統計分析,如果統計的數據量不大,利用insert。。。select將數據移到臨時表中比直接在大表上做統計要效率更高。 五、選擇更合適的表類型 1、如果應用出現比較嚴重的鎖沖突,請考慮是否更改存儲引擎到innodb,行鎖機制可以有效的減少鎖沖突的出現。 2、如果應用查詢操作很多,且對事務完整性要求不嚴格,則可以考慮使用Myisam存儲引擎。 */ SELECT * FROM mytable PROCEDURE ANALYSE(16,256); /* 其他優化措施 使用連接池============================================== 對於訪問數據庫來說,建立連接的代價比較昂貴,因此,我們有必要建立"連接池"以提高訪問的性能。 我們可以把連接當作對象或者設備,池中又有許多已經建立的連接,訪問本來需要與數據庫的連接的地方,都改為和池相連,池臨時分配連接供訪問使用,結果返回后,訪問將連接交還。 減少對Mysql的訪問============================================== 一、避免對同一數據做重復檢索: 應用中需要理清楚對數據庫的訪問邏輯,需要對相同表的訪問,盡量集中在相同sql訪問,一次提取結果,減少對數據庫的重復訪問。 二、使用mysql query cache: 作用:查詢緩存存儲SELECT查詢的文本以及發送給客戶端的相應結果。如果隨后收到一個相同的查詢,服務器從查詢緩存中重新得到查詢結果,而不再需要解析和執行查詢。 適用范圍:不發生數據更新的表。當表更改(包括表結構和表數據)后,查詢緩存值的相關條目被清空。 查詢緩存的主要參數設置: */ SHOW VARIABLES LIKE '%query_cache%'; -- 也可以寫成 SHOW VARIABLES WHERE Variable_name LIKE '%query_cache%'; /* have_query_cache 表明服務器在安裝使已經配置了高速緩存 query_cache_size 表明緩存區大小,單位為字節(1024字節為1KB) query_cache_type 值從0到2,含義分別為 0或者off(緩存關閉) 1或者on(緩存打開,使用sql_no_cache的select除外) 2或者demand(只有帶sql_cache的select語句提供高速緩存) */ SET GLOBAL query_cache_size=1024*50; -- 設置查詢緩存大小,單位字節,1024字節為 1KB,query_cache_size大小的設置必須大於40KB -- 在 SHOW STATUS 中,你可以監視查詢緩存的性能 SHOW STATUS LIKE '%Qcache%'; /* Qcache_queries_in_cache 在緩存中已注冊的查詢數目 Qcache_inserts 被加入到緩存中的查詢數目 Qcache_hits 緩存采樣數數目 Qcache_lowmem_prunes 因為缺少內存而被從緩存中刪除的查詢數目 Qcache_not_cached 沒有被緩存的查詢數目 (不能被緩存的,或由於 QUERY_CACHE_TYPE) Qcache_free_memory 查詢緩存的空閑內存總數 Qcache_free_blocks 查詢緩存中的空閑內存塊的數目 Qcache_total_blocks 查詢緩存中的塊的總數目 三、加cache層: Cache(高速緩存)、Memory(內存)、Hard disk(硬盤)都是數據存取單元,但存取速度卻有很大差異,呈依次遞減的順序。 對於CPU來說,它可以從距離自己最近的Cache高速地存取數據,而不是從內存和硬盤以低幾個數量級的速度來存取數據。 而Cache中所存儲的數據,往往是CPU要反復存取的數據,有特定的機制(或程序)來保證Cache內數據的命中率(Hit Rate)。 因此,CPU存取數據的速度在應用高速緩存后得到了巨大的提高。 因為將數據寫入高速緩存的任務由Cache Manager負責,所以對用戶來說高速緩存的內容肯定是只讀的。 需要你做的工作很少,程序中的SQL語句和直接訪問DBMS時沒有分別,返回的結果也看不出有什么差別。 而數據庫廠商往往會在DB Server的配置文件中提供與Cache相關的參數,通過修改它們,可針對我們的應用優化Cache的管理。 均衡負載============================================== 一、利用mysql 復制分流查詢操作: 利用mysql的主從復制可以有效的分流更新操作和查詢操作,具體的實現是一個主服務器,承擔更新操作,多台從服務器,承擔查詢操作,主從之間通過復制實現數據的同步。 多台從服務器一方面用來確保可用性,一方面可以創建不同的索引滿足不同查詢的需要。 對於主從之間不需要復制全部表的情況,可以通過在主的服務器上搭建一個虛擬的從服務器, 將需要復制到從服務器的表設置成blackhole引擎,然后定義replicate-do-table參數只復制這些表,這樣就過濾出需要復制的binlog, 減少了傳輸binlog的帶寬。因為搭建的虛擬的從服務器只起到過濾binlog的作用,並沒有實際紀錄任何數據,所以對主數據庫服務器的性能影響也非常的有限。 通過復制分流查詢的存在的問題是主數據庫上更新頻繁或者網絡出現問題的時候,主從之間的數據可能存在差異,造成查詢結果的異議,應用在設計的時候需要有所考慮。 二、采用分布式數據庫架構: mysql從5.0.3開始支持分布式事務,當前分布式事務只對Innodb存儲引擎支持。 分布式的數據庫架構適合大數據量,負載高的情況,有良好的擴展性和高可用性。 通過在多台服務器之間分布數據實現在多台服務器之間的負載平均,提高了訪問的執行效率。 具體實現的時候,可以使用mysql的Cluster功能(NDB引擎)或者自己編寫程序來實現全局事務。 */