MySQL數據庫:SQL語句優化、性能優化詳細總結

轉載地址:

https://blog.csdn.net/a745233700/article/details/85265665

數據庫最常用的優化方式有:SQL語句和索引、數據庫表結構、系統配置、硬件。

優化效果:SQL語句和索引 > 數據庫表結構 > 系統配置 > 硬件,但成本從低到高。

數據庫的優化方法小結:

(1)設計符合範式的數據庫。

(2)選擇合適的存儲引擎。

(2)SQL語句優化;

(3)索引優化:高分離字段建立索引。

(4)SQL表結構、字段優化。

(5)數據庫參數優化:IO參數、CPU參數。

(6)延遲加載、設置緩存與緩存參數優化。

(7)分庫分表:垂直切分與水平切分。

(8)分區:將表的數據按照特定的規則放在不同的分區,提高磁盤的IO效率,提高數據庫的性能。

(9)主從複製與讀寫分離:三個主要線程與bin-log文件、relay_log文件,主數據庫負責寫操作,從數據庫負責讀操作。

(10)負載均衡。

(11)數據庫集羣。

(12)硬件。
 

1、寫出統一的SQL語句:

對於以下兩句SQL語句,很多人人認爲是相同的,但是,數據庫查詢優化器認爲是不同的。

select * from dual
select * From dual
雖然只是大小寫不同,查詢分析器就認爲是兩句不同的SQL語句,必須進行兩次解析。生成2個執行計劃。所以作爲程序員,應該保證相同的查詢語句在任何地方都一致,多一個空格都不行。

2、應儘量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。

3、對查詢進行優化,應儘量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。

4、應儘量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描。如:

select id from t where num is null

可以在num上設置默認值0,確保表中num列沒有null值,然後這樣查詢:

select id from t where num=0

5、避免在where子句中使用or來連接條件,如果一個字段有索引,一個字段沒有索引,將導致引擎放棄使用索引而進行全表掃描。

6、前導模糊查詢將導致全表掃描:

select id from t where name like ‘%c%’

下面使用索引

select id from t where name like ‘c%’

7、not in 也要慎用,否則會導致全表掃描;對於連續的數值,能用 between 就不要用 in 了,儘量使用exists代替in。

8、如果在 where 子句中使用參數,也會導致全表掃描。因爲SQL只有在運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然而,如果在編譯時建立訪問計劃,變量的值還是未知的,因而無法作爲索引選擇的輸入項。如下面語句將進行全表掃描:

select id from t where num=@num

可以改爲強制查詢使用索引:

select id from t with(index(索引名)) where num=@num

7、應儘量避免在 where 子句中對字段進行表達式與函數或其他表達式運算操作,這將導致引擎放棄使用索引而進行全表掃描。如:

(1)select id from t where num/2=100

應改爲:select id from t where num=100*2

(2)select id from t where substring(name,1,3)=’abc’ –name以abc開頭的id

應改爲:select id from t where name like ‘abc%’

(3)select id from t where datediff(day,createdate,’2005-11-30′)=0 –’2005-11-30′生成的id

應改爲:select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1

8、Update 語句,如果只更改1、2個字段,不要Update全部字段,否則頻繁調用會引起明顯的性能消耗,同時帶來大量日誌。

9、在使用索引字段作爲條件時,如果該索引是複合索引,那麼必須使用到該索引中的第一個字段作爲條件時才能保證系統使用該索引,否則該索引將不會被使用,並且應儘可能的讓字段順序與索引順序相一致。

10、並不是所有索引對查詢都有效,SQL是根據表中數據來進行查詢優化的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引。如一表中有字段 sex,male、female幾乎各一半,那麼即使在sex上建了索引也對查詢效率起不了作用。

11、索引並不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因爲 insert 或 update 時有可能會重建索引。一個表的索引數較好不要超過6個。

12、應儘可能的避免更新 clustered 索引數據列,因爲 clustered 索引數據列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調整,會耗費相當大的資源。

13、儘量使用數字型字段,若只含數值信息的字段儘量不要設計爲字符型,這會降低查詢和連接的性能,並會增加存儲開銷。這是因爲引擎在處理查詢和連接時會逐個比較字符串中每一個字符,而對於數字型而言只需要比較一次就夠了。

14、儘可能的使用 varchar/nvarchar 代替 char/nchar ,因爲首先變長字段存儲空間小,可以節省存儲空間,其次對於查詢來說,在一個相對較小的字段內搜索效率顯然要高些。

15、任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。

16、對於多張大數據量(這裏幾百條就算大了)的表JOIN,要先分頁再JOIN,否則邏輯讀會很高,性能很差。

17、儘量使用表變量來代替臨時表。

18、考慮使用“臨時表”暫存中間結果。臨時表並不是不可使用,適當地使用它們可以使某些查詢更有效,例如,當需要重複引用大型表或常用表中的某個數據集時。將臨時結果暫存在臨時表,後面的查詢就在tempdb中查詢了,這可以避免程序中多次掃描主表,也大大減少了程序執行中“共享鎖”阻塞“更新鎖”,減少了阻塞,提高了併發性能。但是,對於一次性事件,較好使用導出表。

19、在新建臨時表時,如果一次性插入數據量很大,那麼可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數據量不大,爲了緩和系統表的資源,應先create table,然後insert。

20、如果使用到了臨時表,在存儲過程的最後務必將所有的臨時表顯式刪除,先 truncate table ,然後 drop table ,這樣可以避免系統表的較長時間鎖定。

21、避免頻繁創建和刪除臨時表,以減少系統表資源的消耗。

22、儘量避免使用遊標,因爲遊標的效率較差。與臨時表一樣,遊標並不是不可使用。對小型數據集使用 FAST_FORWARD 遊標通常要優於其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數據時。在結果集中包括“合計”的例程通常要比使用遊標執行的速度快。

23、在所有的存儲過程和觸發器的開始處設置 SET NOCOUNT ON ,在結束時設置 SET NOCOUNT OFF 。

24、儘量避免向客戶端返回大數據量。

25、儘量避免大事務操作,提高系統併發能力。

26、用where子句替換Having子句:

避免使用having子句,having只會在檢索出所有記錄之後纔會對結果集進行過濾,這個處理需要排序,如果能通過where子句限制記錄的數目,就可以減少這方面的開銷。on、where、having這三個都可以加條件的子句,on是最先執行,where次之,having最後。

27、使用Truncate替代delete:

當需要刪除全表的記錄時使用Truncate替代delete。在通常情況下, 回滾段(rollback segments ) 用來存放可以被恢復的信息. 如果你沒有COMMIT事務,ORACLE會將數據恢復到刪除之前的狀態(準確地說是恢復到執行刪除命令之前的狀況) 而當運用TRUNCATE時, 回滾段不再存放任何可被恢復的信息.當命令運行後,數據不能被恢復.因此很少的資源被調用,執行時間也會很短。

28、使用表的別名:

當在SQL語句中連接多個表時, 請使用表的別名並把別名前綴於每個Column上.這樣一來,就可以減少解析的時間並減少那些由Column歧義引起的語法錯誤。

29、使用union all 替換 union:

當SQL語句需要union兩個查詢結果集合時,這兩個結果集合會以union all的方式被合併,然後再輸出最終結果前進行排序。如果用union all替代料union,這樣排序就不是不要了,效率就會因此得到提高. 需要注意的是,UNION ALL 將重複輸出兩個結果集合中相同記錄。

30、用where替代order by:

ORDER BY 子句只在兩種嚴格的條件下使用索引:
(1)ORDER BY中所有的列必須包含在相同的索引中並保持在索引中的排列順序;
(2)ORDER BY中所有的列必須定義爲非空; 

低效: (索引不被使用) 
SELECT DEPT_CODE FROM  DEPT  ORDER BY  DEPT_TYPE 
高效: (使用索引) 
SELECT DEPT_CODE  FROM  DEPT  WHERE  DEPT_TYPE > 0

31、避免索引列的類型轉換:

假設EMP_TYPE是一個字符類型的索引列. 
SELECT …  FROM EMP  WHERE EMP_TYPE = 123 
這個語句被轉換爲: 
SELECT …  FROM EMP  WHERE EMP_TYPE=‘123’ 
因爲內部發生的類型轉換, 這個索引將不會被用到! 爲了避免ORACLE對你的SQL進行隱式的類型轉換, 最好把類型轉換用顯式表現出來. 注意當字符和數值比較時, ORACLE會優先轉換數值類型到字符類型。

32、優化Group by:

提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉.下面兩個查詢返回相同結果但第二個明顯就快了許多。

低效: 
SELECT JOB , AVG(SAL) 
FROM EMP 
GROUP by JOB 
HAVING JOB = ‘PRESIDENT' 
OR JOB = ‘MANAGER' 
高效: 
SELECT JOB , AVG(SAL) 
FROM EMP 
WHERE JOB = ‘PRESIDENT' 
OR JOB = ‘MANAGER' 
GROUP by JOB

33、避免使用耗費資源的操作:

帶有DISTINCT,UNION,MINUS,INTERSECT,ORDER BY的SQL語句會啓動SQL引擎執行耗費資源的排序(SORT)功能. DISTINCT需要一次排序操作, 而其他的至少需要執行兩次排序. 通常, 帶有UNION, MINUS , INTERSECT的SQL語句都可以用其他方式重寫. 如果你的數據庫的SORT_AREA_SIZE調配得好, 使用UNION , MINUS, INTERSECT也是可以考慮的, 畢竟它們的可讀性很強。

34、在運行代碼中,儘量使用PreparedStatement來查詢,不要用Statement。

三、MySQL 常用的索引優化方法:

這部分可以閱讀這篇文章:MySQL數據庫:索引(三):索引的使用及優化

 

四、MySQL數據庫的優化目標、常見誤區和基本原則:

1、優化目標:

MySQL數據庫是常見的兩個瓶頸是CPU和I/O的瓶頸,CPU在飽和的時候一般發生在數據裝入內存或從磁盤上讀取數據時候。磁盤I/O瓶頸發生在裝入數據遠大於內存容量的時候。

(1)減少 I/O 次數:

I/O永遠是數據庫最容易瓶頸的地方,這是由數據庫的職責所決定的,大部分數據庫操作中超過90%的時間都是 IO 操作所佔用的,減少 IO 次數是 SQL 優化中需要第一優先考慮,當然,也是收效最明顯的優化手段。

(2)降低 CPU 計算:

除了 IO 瓶頸之外,SQL優化中需要考慮的就是 CPU 運算量的優化了。order by, group by,distinct … 都是消耗 CPU 的大戶(這些操作基本上都是 CPU 處理內存中的數據比較運算)。當我們的 IO 優化做到一定階段之後,降低 CPU 計算也就成爲了我們 SQL 優化的重要目標。

2、常見誤區:

(1)count(1)和count(primary_key) 優於 count(*):

很多人爲了統計記錄條數,就使用 count(1) 和 count(primary_key) 而不是 count(*) ,他們認爲這樣性能更好,其實這是一個誤區。對於有些場景,這樣做可能性能會更差,應爲數據庫對 count(*) 計數操作做了一些特別的優化。如在MyISAM引擎中,會對錶的總行數進行記錄,使用count(*)可以直接取出該值。

(2)count(column) 和 count(*) 是一樣的:

實際上,count(column) 和 count(*) 是一個完全不一樣的操作,所代表的意義也完全不一樣。count(column) 是表示結果集中有多少個column字段不爲空的記錄,只處理非空值。count(*) 是表示整個結果集有多少條記錄,不會跳過null值。

(3)select a,b from … 比 select a,b,c from … 可以讓數據庫訪問更少的數據量:

實際上,大多數關係型數據庫都是按照行(row)的方式存儲,而數據存取操作都是以一個固定大小的IO單元(被稱作 block 或者 page)爲單位,一般爲4KB,8KB… 大多數時候,每個IO單元中存儲了多行,每行都是存儲了該行的所有字段(lob等特殊類型字段除外)。所以,我們是取一個字段還是多個字段,實際上數據庫在表中需要訪問的數據量其實是一樣的。

當然,也有例外情況,那就是我們的這個查詢在索引中就可以完成,也就是說當只取 a,b兩個字段的時候,不需要回表,而c這個字段不在使用的索引中,需要回表取得其數據。在這樣的情況下,二者的IO量會有較大差異。

(4)order by 一定需要排序操作:

我們知道索引數據實際上是有序的,如果我們的需要的數據和某個索引的順序一致,而且我們的查詢又通過這個索引來執行,那麼數據庫一般會省略排序操作,而直接將數據返回,因爲數據庫知道數據已經滿足我們的排序需求了。實際上,利用索引來優化有排序需求的 SQL,是一個非常重要的優化手段。

延伸閱讀:MySQL ORDER BY 的實現分析 ,MySQL 中 GROUP BY 基本實現原理 以及 MySQL DISTINCT 的基本實現原理 這3篇文章中有更爲深入的分析,尤其是第一篇。

(5)執行計劃中有 filesort 就會進行磁盤文件排序:

有這個誤區其實並不能怪我們,而是因爲 MySQL 開發者在用詞方面的問題。filesort 是我們在使用 explain 命令查看一條 SQL 的執行計劃的時候可能會看到在 “Extra” 一列顯示的信息。實際上,只要一條 SQL 語句需要進行排序操作,都會顯示“Using filesort”,這並不表示就會有文件排序操作。

延伸閱讀:理解 MySQL Explain 命令輸出中的filesort,我在這裏有更爲詳細的介紹

3、基本原則:

(1)儘量少 join:

MySQL 的優勢在於簡單,但這在某些方面其實也是其劣勢。MySQL 優化器效率高,但是由於其統計信息的量有限,優化器工作過程出現偏差的可能性也就更多。對於複雜的多表 Join,一方面由於其優化器受限,再者在 Join 這方面所下的功夫還不夠,所以性能表現離 Oracle 等關係型數據庫前輩還是有一定距離。但如果是簡單的單表查詢,這一差距就會極小甚至在有些場景下要優於這些數據庫前輩。

(2)儘量少排序:

排序操作會消耗較多的 CPU 資源,所以減少排序可以在緩存命中率高等 IO 能力足夠的場景下會較大影響 SQL 的響應時間。對於MySQL來說,減少排序有多種辦法,比如:

上面誤區中提到的通過利用索引來排序的方式進行優化;

減少參與排序的記錄條數;

非必要不對數據進行排序。

(3)儘量避免 select *:

很多人看到這一點後覺得比較難理解,上面不是在誤區中剛剛說 select 子句中字段的多少並不會影響到讀取的數據嗎?
是的,大多數時候並不會影響到 IO 量,但是當我們還存在 order by 操作的時候,select 子句中的字段多少會在很大程度上影響到我們的排序效率,此外,上面誤區中不是也說了,只是大多數時候是不會影響到 IO 量,當我們的查詢結果僅僅只需要在索引中就能找到的時候,還是會極大減少 IO 量的。

(4)儘量用 join 代替子查詢:

雖然 Join 性能並不佳,但是和 MySQL 的子查詢比起來還是有非常大的性能優勢。

(5)儘量少 or:

當 where 子句中存在多個條件以“或”並存的時候,MySQL 的優化器並沒有很好的解決其執行計劃優化問題,再加上 MySQL 特有的 SQL 與 Storage 分層架構方式,造成了其性能比較低下,很多時候使用 union all 或者是union(必要的時候)的方式來代替“or”會得到更好的效果。

(6)儘量用 union all 代替 union:

union 和 union all 的差異主要是前者需要將兩個(或者多個)結果集合並後再進行唯一性過濾操作,這就會涉及到排序,增加大量的 CPU 運算,加大資源消耗及延遲。所以當我們可以確認不可能出現重複結果集或者不在乎重複結果集的時候,儘量使用 union all 而不是 union。

(7)儘量早過濾:

這一優化策略其實最常見於索引的優化設計中(將過濾性更好的字段放得更靠前)。
在 SQL 編寫中同樣可以使用這一原則來優化一些 Join 的 SQL。比如我們在多個表進行分頁數據查詢的時候,我們最好是能夠在一個表上先過濾好數據分好頁,然後再用分好頁的結果集與另外的表 Join,這樣可以儘可能多的減少不必要的 IO 操作,大大節省 IO 操作所消耗的時間。

(8)避免類型轉換:

這裏所說的“類型轉換”是指 where 子句中出現 column 字段的類型和傳入的參數類型不一致的時候發生的類型轉換:

(9)優先優化高併發的 SQL,而不是執行頻率低某些“大”SQL:

對於破壞性來說,高併發的 SQL 總是會比低頻率的來得大,因爲高併發的 SQL 一旦出現問題,甚至不會給我們任何喘息的機會就會將系統壓跨。而對於一些雖然需要消耗大量 IO 而且響應很慢的 SQL,由於頻率低,即使遇到,最多就是讓整個系統響應慢一點,但至少可能撐一會兒,讓我們有緩衝的機會。

(10)從全局出發優化,而不是片面調整:

SQL 優化不能是單獨針對某一個進行,而應充分考慮系統中所有的 SQL,尤其是在通過調整索引優化 SQL 的執行計劃的時候,千萬不能顧此失彼,因小失大。

(11)儘可能對每一條運行在數據庫中的SQL進行 explain:
優化 SQL,需要做到心中有數,知道 SQL 的執行計劃才能判斷是否有優化餘地,才能判斷是否存在執行計劃問題。在對數據庫中運行的 SQL 進行了一段時間的優化之後,很明顯的問題 SQL 可能已經很少了,大多都需要去發掘,這時候就需要進行大量的 explain 操作收集執行計劃,並判斷是否需要進行優化。

 

五、MySQL數據庫的表結構優化:

由於MySQL數據庫是基於行(Row)存儲的數據庫,而數據庫操作 IO 的時候是以 page(block)的方式,也就是說,如果我們每條記錄所佔用的空間量減小,就會使每個page中可存放的數據行數增大,那麼每次 IO 可訪問的行數也就增多了。反過來說,處理相同行數的數據,需要訪問的 page 就會減少,也就是 IO 操作次數降低,直接提升性能。此外,由於我們的內存是有限的,增加每個page中存放的數據行數,就等於增加每個內存塊的緩存數據量,同時還會提升內存換中數據命中的機率,也就是緩存命中率。

1、數據類型選擇:

數據庫操作中最爲耗時的操作就是 IO 處理,大部分數據庫操作 90% 以上的時間都花在了 IO 讀寫上面。所以儘可能減少 IO 讀寫量,可以在很大程度上提高數據庫操作的性能。我們無法改變數據庫中需要存儲的數據,但是我們可以在這些數據的存儲方式方面花一些心思。下面的這些關於字段類型的優化建議主要適用於記錄條數較多,數據量較大的場景,因爲精細化的數據類型設置可能帶來維護成本的提高,過度優化也可能會帶來其他的問題:

(1)數字類型:非萬不得已不要使用DOUBLE,不僅僅只是存儲長度的問題,同時還會存在精確性的問題。同樣,固定精度的小數,也不建議使用DECIMAL,建議乘以固定倍數轉換成整數存儲,可以大大節省存儲空間,且不會帶來任何附加維護成本。對於整數的存儲,在數據量較大的情況下,建議區分開 TINYINT / INT / BIGINT 的選擇,因爲三者所佔用的存儲空間也有很大的差別,能確定不會使用負數的字段,建議添加unsigned定義。當然,如果數據量較小的數據庫,也可以不用嚴格區分三個整數類型。

int類型只增主鍵字段=>4字節=>每個字節8位=>32位,在CPU加載一條指令的時候,4字節是和CPU寄存器的運算有關,如:64位,由於之前的系統一般都是32位的,所以在運算4字節的數據是剛好的,效率最高,而現今我們系統基本都是64位的時候,其實沒有更好的利用好CPU運算,所以在設計表字段建議,使用8字節的主鍵bigint,而不是直接使用int來做主鍵。

(2)字符類型:非萬不得已不要使用 TEXT 數據類型,其處理方式決定了他的性能要低於char或者是varchar類型的處理。定長字段,建議使用 CHAR 類型,不定長字段儘量使用 VARCHAR,且僅僅設定適當的最大長度,而不是非常隨意的給一個很大的最大長度限定,因爲不同的長度範圍,MySQL也會有不一樣的存儲處理。

char(10) 不管該字段是否存儲數據,都佔10個字符的存儲空間,char(10) 同時存在一個坑,就是存儲abc數據後改數據庫字段的值爲“abc  7個空格 ”,在精準查詢(where)就必須帶上後面的7個空格。varchar 不存的時候不佔空間,存多長數據就佔多少空間。

(3)時間類型:儘量使用TIMESTAMP類型,因爲其存儲空間只需要 DATETIME 類型的一半。對於只需要精確到某一天的數據類型,建議使用DATE類型,因爲他的存儲空間只需要3個字節,比TIMESTAMP還少。不建議通過INT類型類存儲一個unix timestamp 的值,因爲這太不直觀,會給維護帶來不必要的麻煩,同時還不會帶來任何好處。

(4)ENUM & SET:對於狀態字段,可以嘗試使用 ENUM 來存放,因爲可以極大的降低存儲空間,而且即使需要增加新的類型,只要增加於末尾,修改結構也不需要重建表數據。如果是存放可預先定義的屬性數據呢?可以嘗試使用SET類型,即使存在多種屬性,同樣可以遊刃有餘,同時還可以節省不小的存儲空間。

(5)LOB類型:強烈反對在數據庫中存放 LOB 類型數據,雖然數據庫提供了這樣的功能,但這不是他所擅長的,我們更應該讓合適的工具做他擅長的事情,才能將其發揮到極致。在數據庫中存儲 LOB 數據就像讓一個多年前在學校學過一點Java的營銷專業人員來寫 Java 代碼一樣。

(6)字符編碼:字符集直接決定了數據在MySQL中的存儲編碼方式,由於同樣的內容使用不同字符集表示所佔用的空間大小會有較大的差異,所以通過使用合適的字符集,可以幫助我們儘可能減少數據量,進而減少IO操作次數。

①純拉丁字符能表示的內容,沒必要選擇 latin1 之外的其他字符編碼,因爲這會節省大量的存儲空間;

②如果我們可以確定不需要存放多種語言,就沒必要非得使用UTF8或者其他UNICODE字符類型,這回造成大量的存儲空間浪費;

③MySQL的數據類型可以精確到字段,所以當我們需要大型數據庫中存放多字節數據的時候,可以通過對不同表不同字段使用不同的數據類型來較大程度減小數據存儲量,進而降低 IO 操作次數並提高緩存命中率。

(7)適當拆分:

有些時候,我們可能會希望將一個完整的對象對應於一張數據庫表,這對於應用程序開發來說是很有好的,但是有些時候可能會在性能上帶來較大的問題。當我們的表中存在類似於 TEXT 或者是很大的 VARCHAR類型的大字段的時候,如果我們大部分訪問這張表的時候都不需要這個字段,我們就該義無反顧的將其拆分到另外的獨立表中,以減少常用數據所佔用的存儲空間。這樣做的一個明顯好處就是每個數據塊中可以存儲的數據條數可以大大增加,既減少物理 IO 次數,也能大大提高內存中的緩存命中率。

2、上面幾點的優化都是爲了減少每條記錄的存儲空間大小,讓每個數據庫中能夠存儲更多的記錄條數,以達到減少 IO 操作次數,提高緩存命中率。下面這個優化建議可能很多開發人員都會覺得不太理解,因爲這是典型的反範式設計,而且也和上面的幾點優化建議的目標相違背。

(1)適度冗餘:

爲什麼我們要冗餘?這不是增加了每條數據的大小,減少了每個數據塊可存放記錄條數嗎?確實,這樣做是會增大每條記錄的大小,降低每條記錄中可存放數據的條數,但是在有些場景下我們仍然還是不得不這樣做:

①被頻繁引用且只能通過 Join 2張(或者更多)大表的方式才能得到的獨立小字段:這樣的場景由於每次Join僅僅只是爲了取得某個小字段的值,Join到的記錄又大,會造成大量不必要的 IO,完全可以通過空間換取時間的方式來優化。不過,冗餘的同時需要確保數據的一致性不會遭到破壞,確保更新的同時冗餘字段也被更新。

(2)儘量使用 NOT NULL:

NULL 類型比較特殊,SQL 難優化。雖然 MySQL NULL類型和 Oracle 的NULL 有差異,會進入索引中,但如果是一個組合索引,那麼這個NULL 類型的字段會極大影響整個索引的效率。很多人覺得 NULL 會節省一些空間,所以儘量讓NULL來達到節省IO的目的,但是大部分時候這會適得其反,雖然空間上可能確實有一定節省,倒是帶來了很多其他的優化問題,不但沒有將IO量省下來,反而加大了SQL的IO量。所以儘量確保 DEFAULT 值不是 NULL,也是一個很好的表結構設計優化習慣。

 

六、MySQL數據庫的緩存參數優化:

數據庫屬於 IO 密集型的應用程序,其主要職責就是數據的管理及存儲工作。而我們知道,從內存中讀取一個數據庫的時間是微秒級別,而從一塊普通硬盤上讀取一個IO是在毫秒級別,二者相差3個數量級。所以,要優化數據庫,首先第一步需要優化的就是 IO,儘可能將磁盤IO轉化爲內存IO。本文先從 MySQL 數據庫IO相關參數(緩存參數)的角度來看看可以通過哪些參數進行IO優化:

1、query_cache_size / query_cache_type (global):

Query cache 作用於整個 MySQL Instance,主要用來緩存 MySQL 中的 ResultSet,也就是一條SQL語句執行的結果集,所以僅僅只能針對select語句。當我們打開了 Query Cache 功能,MySQL在接受到一條select語句的請求後,如果該語句滿足Query Cache的要求(未顯式說明不允許使用Query Cache,或者已經顯式申明需要使用Query Cache),MySQL 會直接根據預先設定好的HASH算法將接受到的select語句以字符串方式進行hash,然後到Query Cache 中直接查找是否已經緩存。也就是說,如果已經在緩存中,該select請求就會直接將數據返回,從而省略了後面所有的步驟(如 SQL語句的解析,優化器優化以及向存儲引擎請求數據等),極大的提高性能。

當然,Query Cache 也有一個致命的缺陷,那就是當某個表的數據有任何任何變化,都會導致所有引用了該表的select語句在Query Cache 中的緩存數據失效。所以,當我們的數據變化非常頻繁的情況下,使用Query Cache 可能會得不償失。

Query Cache的使用需要多個參數配合,其中最爲關鍵的是 query_cache_size 和 query_cache_type ,前者設置用於緩存 ResultSet 的內存大小,後者設置在何場景下使用 Query Cache。在以往的經驗來看,如果不是用來緩存基本不變的數據的MySQL數據庫,query_cache_size 一般 256MB 是一個比較合適的大小。當然,這可以通過計算Query Cache的命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))來進行調整。query_cache_type可以設置爲0(OFF),1(ON)或者2(DEMOND),分別表示完全不使用query cache,除顯式要求不使用query cache(使用sql_no_cache)之外的所有的select都使用query cache,只有顯示要求才使用query cache(使用sql_cache)。

2、binlog_cache_size (global):

Binlog Cache 用於在打開了二進制日誌(binlog)記錄功能的環境,是 MySQL 用來提高binlog的記錄效率而設計的一個用於短時間內臨時緩存binlog數據的內存區域。一般來說,如果我們的數據庫中沒有什麼大事務,寫入也不是特別頻繁,2MB~4MB是一個合適的選擇。但是如果我們的數據庫大事務較多,寫入量比較大,可與適當調高binlog_cache_size。同時,我們可以通過binlog_cache_use 以及 binlog_cache_disk_use來分析設置的binlog_cache_size是否足夠,是否有大量的binlog_cache由於內存大小不夠而使用臨時文件(binlog_cache_disk_use)來緩存了。

3、key_buffer_size (global):

Key Buffer 可能是大家最爲熟悉的一個 MySQL 緩存參數了,尤其是在 MySQL 沒有更換默認存儲引擎的時候,很多朋友可能會發現,默認的 MySQL 配置文件中設置最大的一個內存參數就是這個參數了。key_buffer_size 參數用來設置用於緩存 MyISAM存儲引擎中索引文件的內存區域大小。如果我們有足夠的內存,這個緩存區域最好是能夠存放下我們所有的 MyISAM 引擎表的所有索引,以儘可能提高性能。此外,當我們在使用MyISAM 存儲的時候有一個及其重要的點需要注意,由於 MyISAM 引擎的特性限制了他僅僅只會緩存索引塊到內存中,而不會緩存表數據庫塊。所以,我們的 SQL 一定要儘可能讓過濾條件都在索引中,以便讓緩存幫助我們提高查詢效率。

4、bulk_insert_buffer_size (thread):

和key_buffer_size一樣,這個參數同樣也僅作用於使用 MyISAM存儲引擎,用來緩存批量插入數據的時候臨時緩存寫入數據。當我們使用如下幾種數據寫入語句的時候,會使用這個內存區域來緩存批量結構的數據以幫助批量寫入數據文件:

insert … select …
insert … values (…) ,(…),(…)…
load data infile… into… (非空表)

5、innodb_buffer_pool_size(global):

當我們使用InnoDB存儲引擎的時候,innodb_buffer_pool_size 參數可能是影響我們性能的最爲關鍵的一個參數了,他用來設置用於緩存 InnoDB 索引及數據塊的內存區域大小,類似於 MyISAM 存儲引擎的 key_buffer_size 參數,當然,可能更像是 Oracle 的 db_cache_size。簡單來說,當我們操作一個 InnoDB 表的時候,返回的所有數據或者去數據過程中用到的任何一個索引塊,都會在這個內存區域中走一遭。和key_buffer_size 對於 MyISAM 引擎一樣,innodb_buffer_pool_size 設置了 InnoDB 存儲引擎需求最大的一塊內存區域的大小,直接關係到 InnoDB存儲引擎的性能,所以如果我們有足夠的內存,儘可將該參數設置到足夠打,將儘可能多的 InnoDB 的索引及數據都放入到該緩存區域中,直至全部。我們可以通過 (Innodb_buffer_pool_read_requests – Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100% 計算緩存命中率,並根據命中率來調整 innodb_buffer_pool_size 參數大小進行優化。

6、innodb_additional_mem_pool_size(global):

innodb_additional_mem_pool_size 設置了InnoDB存儲引擎用來存放數據字典信息以及一些內部數據結構的內存空間大小,所以當我們一個MySQL Instance中的數據庫對象非常多的時候,是需要適當調整該參數的大小以確保所有數據都能存放在內存中提高訪問效率的。這個參數大小是否足夠還是比較容易知道的,因爲當過小的時候,MySQL 會記錄 Warning 信息到數據庫的 error log 中,這時候你就知道該調整這個參數大小了。

7、innodb_log_buffer_size (global):

這是 InnoDB 存儲引擎的事務日誌所使用的緩衝區。類似於 Binlog Buffer,InnoDB 在寫事務日誌的時候,爲了提高性能,也是先將信息寫入 Innofb Log Buffer 中,當滿足 innodb_flush_log_trx_commit 參數所設置的相應條件(或者日誌緩衝區寫滿)之後,纔會將日誌寫到文件(或者同步到磁盤)中。可以通過 innodb_log_buffer_size 參數設置其可以使用的最大內存空間。

innodb_flush_log_trx_commit 參數對 InnoDB Log 的寫入性能有非常關鍵的影響。該參數可以設置爲0,1,2,解釋如下:

0:log buffer中的數據將以每秒一次的頻率寫入到log file中,且同時會進行文件系統到磁盤的同步操作,但是每個事務的commit並不會觸發任何log buffer 到log file的刷新或者文件系統到磁盤的刷新操作;
1:在每次事務提交的時候將log buffer 中的數據都會寫入到log file,同時也會觸發文件系統到磁盤的同步;
2:事務提交會觸發log buffer 到log file的刷新,但並不會觸發磁盤文件系統到磁盤的同步。此外,每秒會有一次文件系統到磁盤同步操作。

此外,MySQL文檔中還提到,這幾種設置中的每秒同步一次的機制,可能並不會完全確保非常準確的每秒就一定會發生同步,還取決於進程調度的問題。實際上,InnoDB 能否真正滿足此參數所設置值代表的意義正常 Recovery 還是受到了不同 OS 下文件系統以及磁盤本身的限制,可能有些時候在並沒有真正完成磁盤同步的情況下也會告訴 mysqld 已經完成了磁盤同步。

 

8、innodb_max_dirty_pages_pct (global):

這個參數和上面的各個參數不同,他不是用來設置用於緩存某種數據的內存大小的一個參數,而是用來控制在 InnoDB Buffer Pool 中可以不用寫入數據文件中的Dirty Page 的比例(已經被修但還沒有從內存中寫入到數據文件的髒數據)。這個比例值越大,從內存到磁盤的寫入操作就會相對減少,所以能夠一定程度下減少寫入操作的磁盤IO。但是,如果這個比例值過大,當數據庫 Crash 之後重啓的時間可能就會很長,因爲會有大量的事務數據需要從日誌文件恢復出來寫入數據文件中。同時,過大的比例值同時可能也會造成在達到比例設定上限後的 flush 操作“過猛”而導致性能波動很大。

上面這幾個參數是 MySQL 中爲了減少磁盤物理IO而設計的主要參數,對 MySQL 的性能起到了至關重要的作用,下面是幾個參數的建議取值:

query_cache_type : 如果全部使用innodb存儲引擎,建議爲0,如果使用MyISAM 存儲引擎,建議爲2,同時在SQL語句中顯式控制是否是使用query cache;
query_cache_size: 根據 命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))進行調整,一般不建議太大,256MB可能已經差不多了,大型的配置型靜態數據可適當調大;
binlog_cache_size: 一般環境2MB~4MB是一個合適的選擇,事務較大且寫入頻繁的數據庫環境可以適當調大,但不建議超過32MB;
key_buffer_size: 如果不使用MyISAM存儲引擎,16MB足以,用來緩存一些系統表信息等。如果使用 MyISAM存儲引擎,在內存允許的情況下,儘可能將所有索引放入內存,簡單來說就是“越大越好”;
bulk_insert_buffer_size: 如果經常性的需要使用批量插入的特殊語句(上面有說明)來插入數據,可以適當調大該參數至16MB~32MB,不建議繼續增大;
innodb_buffer_pool_size: 如果不使用InnoDB存儲引擎,可以不用調整這個參數,如果需要使用,在內存允許的情況下,儘可能將所有的InnoDB數據文件存放如內存中,同樣將但來說也是“越大越好”;
innodb_additional_mem_pool_size: 一般的數據庫建議調整到8MB~16MB,如果表特別多,可以調整到32MB,可以根據error log中的信息判斷是否需要增大;
innodb_log_buffer_size: 默認是1MB,寫入頻繁的系統可適當增大至4MB~8MB。當然如上面介紹所說,這個參數實際上還和另外的flush參數相關。一般來說不建議超過32MB;
innodb_max_dirty_pages_pct: 根據以往的經驗,重啓恢復的數據如果要超過1GB的話,啓動速度會比較慢,幾乎難以接受,所以建議不大於 1GB/innodb_buffer_pool_size(GB)*100 這個值。當然,如果你能夠忍受啓動時間比較長,而且希望儘量減少內存至磁盤的flush,可以將這個值調整到90,但不建議超過90。

發佈了4 篇原創文章 · 獲贊 4 · 訪問量 1624
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章