1. 程式人生 > >MYSQL設計優化

MYSQL設計優化

本文將從各方面介紹優化mysql設計的一些方式。

1、優化sql語句

(1)定位需要優化的sql語句

1)show status統計SQL語句頻率

對Myisam和Innodb儲存引擎都計數的引數:

SHOW STATUS可以根據需要顯示session級別的統計結果和global級別的統計結果。

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大致的執行比例是多少。
對於更新操作的計數,是對執行次數的計數,不論提交還是回滾都會累加。

對於事務型的引數
1.Com_commit 事務提交次數
2.Com_rollback 事務回滾次數
對於回滾操作非常頻繁的資料庫,可能意味著應用編寫存在問題。

資料庫的基本情況的引數:

1.Connections 試圖連線Mysql伺服器的次數
2.Uptime 伺服器工作時間

3.Slow_queries 慢查詢的次數

2)定位執行效率較低的SQL語句

兩種方式定位執行效率較低的SQL語句:
(1)通過慢查詢日誌定位那些執行效率較低的sql語句(需要查詢結束後),用--log-slow-queries[=file_name]選項啟動時,mysqld寫一個包含所有執行時間超過long_query_time秒的SQL語句的日誌檔案.
(2)使用show processlist命令檢視當前MySQL在進行的執行緒,包括執行緒的狀態,是否鎖表等等,可以實時的檢視SQL執行情況,同時對一些鎖表操作進行優化。

3)EXPLAIN命令分析SQL語句

通過explain或者desc 獲取MySQL如何執行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:       表示表的連線型別
①當表中僅有一行是最佳的連線型別;
②當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      |              |
+--------+---------------+-------+-------+--------------+----------------+-----------+-------+--------+--------------+

(2)sql語句優化方式

1)大批量插入資料 

1)對於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;
對於匯入大量資料到一個空的Myisam表,預設就是先匯入資料然後才建立索引的,所以不用進行設定。

2)對於Innodb型別的表,我們有以下幾種方式可以提高匯入的效率(對Innodb型別的表,上面的方式並不能提高匯入資料的效率)
①因為Innodb型別的表是按照主鍵的順序儲存的,所以將匯入的資料按照主鍵的順序排列,可以有效的提高匯入資料的效率。
如果Innodb表沒有主鍵,那麼系統會預設建立一個內部列作為主鍵,所以如果可以給表建立一個主鍵,將可以利用這個優勢提高匯入資料的效率。
②在匯入資料前執行SET UNIQUE_CHECKS=0,關閉唯一性校驗,在匯入結束後執行SET UNIQUE_CHECKS=1,恢復唯一性校驗,可以提高匯入的效率。
SET UNIQUE_CHECKS=0;
SET UNIQUE_CHECKS=1;
③如果使用自動提交的方式,建議在匯入前執行SET AUTOCOMMIT=0,關閉自動提交,匯入結束後再執行SET AUTOCOMMIT=1,開啟自動提交,也可以提高匯入的效率。
SET AUTOCOMMIT=0;

SET AUTOCOMMIT=1;

2)優化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);

3)優化group by語句

預設情況下,MySQL排序所有GROUP BY col1,col2,....(如同指定了ORDER BY  col1,col2,...)
如果查詢包括GROUP BY但想避免排序結果的消耗,可以指定 ORDER BY NULL禁止排序。
例如:

SELECT * FROM mytable GROUP BY username ORDER BY NULL;

4)優化order by語句

以下情況可以使用索引:
SELECT * FROM t1 ORDER BY key_part1,key_part2,... ;  --order by欄位都為同一組合索引的一部分
SELECT * FROM t1 WHERE key_part1=1 ORDER BY key_part1 DESC, --key_part2 DESC;--where條件和order by使用相同的索引欄位 
SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 DESC;--order by的的所有欄位順序相同
以下情況不使用索引:
1)SELECT * FROM t1 ORDER BY key_part1 DESC, key_part2 ASC;--order by的欄位混合ASC和DESC
2)SELECT * FROM t1 WHERE key2=constant ORDER BY key1;--用於查詢行的關鍵字與ORDER BY中所使用的不相同

3)SELECT * FROM t1 ORDER BY key1, key2;--對不同的關鍵字使用ORDER BY

5)優化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 salesinfo ON customerinfo.CustomerID=salesinfo.CustomerID
WHERE salesinfo.CustomerID IS NULL 

連線(JOIN).. 之所以更有效率一些,是因為 MySQL不需要在記憶體中建立臨時表來完成這個邏輯上的需要兩個步驟的查詢工作。

(3)排程優先順序

MySQL提供語句調節復來改變語句(insert update delete)排程的優先順序,可以根據應用是以查詢還是更新為主,來協調自多個客戶端的操作。
(排程策略的修改主要是針對Myisam儲存引擎的;對於Innodb儲存引擎,語句的執行是由獲得行鎖的順序來決定的)
預設排程策略
1)寫入操作優先於讀取操作。
2)對某張資料表的寫入操作某一時刻只能發生一次,寫入請求根據它們到達的次序來處理。
3)對某張資料表的多個讀取操作可以同時地進行。
查詢為主的排程策略
1)LOW_PRIORITY 關鍵字應用於 DELETE 、 INSERT 、 LOAD DATA 、 REPLACE和UPDATE 。
2)HIGH_PRIORITY關鍵字應用於SELECT。
3)DELAYED關鍵字應用於INSERT和REPLACE語句。
更新等操作被設定為LOW_PRIORITY(低優先順序)請求,那麼讀取操作優先順序會高於寫操作。 

SELECT操作被設定為HIGH_PRIORITY(高優先順序),則也會調整SELECT到正在等待的寫入操作之前。

如果寫入者在等待的時候,第二個讀取者到達了,那麼就允許第二個讀取者插到寫入者之前。只有在沒有其它的讀取者的時候,才允許寫入者開始操作。

設定方式:

1)啟動方式

如果使用--low-priority-updates選項來啟動伺服器,則所有支援LOW_PRIORITY選項的語句都預設地按照低優先順序來處理。

2)sql方式

通過使用INSERT HIGH_PRIORITY來把INSERT語句提高到正常的寫入優先順序,可以消除該選項對單個INSERT語句的影響。

如INSERT HIGH_PRIORITY INTO mytable(id, username, city, age) VALUES(7, 'name7', 'city7', 70);

注意:

可能存在LOW_PRIORITY的寫入操作永遠被阻塞的情況。

2、優化資料表

(1)優化表的資料型別

函式PROCEDURE ANALYSE()可以對資料表中的列的資料型別提出優化建議。(比如冗餘欄位的建議)
語法:
SELECT * FROM tbl_name PROCEDURE ANALYSE(); --輸出的對資料表中的每一列的資料型別提出優化建議

SELECT * FROM tbl_name PROCEDURE ANALYSE(16,256);--不要為那些包含的值多於16個和256位元組的ENUM型別提出建議。(如果沒有這樣的限制,輸出資訊可能很長;且ENUM定義通常很難閱讀)

使用如:mysql> DESC user_account;

+-----------+------------------+------+-----+---------+----------------+
| Field     | Type             | Null | Key | Default | Extra          |
+-----------+------------------+------+-----+---------+----------------+
| USERID    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| USERNAME  | varchar(10)      | NO   |     | NULL    |                |
| PASSSWORD | varchar(30)      | NO   |     | NULL    |                |
| GROUPNAME | varchar(10)      | YES  |     | NULL    |                |
+-----------+------------------+------+-----+---------+----------------+

mysql> select * from user_account PROCEDURE ANALYSE(1)\G;

*************************** 1. row ***************************
             Field_name: ibatis.user_account.USERID
              Min_value: 1
              Max_value: 103
             Min_length: 1
             Max_length: 3
       Empties_or_zeros: 0
                  Nulls: 0
Avg_value_or_avg_length: 51.7500
                    Std: 50.2562
Optimal_fieldtype: TINYINT(3) UNSIGNED NOT NULL

......

第一行輸出分析出ibatis.user_account.USERID列最小值1,最大值103,最小長度1,最大長度3...,欄位的優化建議:將該欄位的資料型別改成TINYINT(3) UNSIGNED NOT NULL

(2)拆分表提高訪問效率

主要是針對Myisam型別的表,拆分有兩種方式:
縱向拆分:
縱向拆分是隻按照應用訪問的頻度,將表中經常訪問的欄位和不經常訪問的欄位拆分成兩個表,經常訪問的欄位儘量是定長的,這樣可以有效的提高表的查詢和更新的效率。
橫向拆分:
橫向拆分是指按照應用的情況,有目的的將資料橫向拆分成幾個表或者通過分割槽分到多個分割槽中,這樣可以有效的避免Myisam表的讀取和更新導致的鎖問題。

(3)規範化和逆規範化

根據實際情況考慮以下兩個需求:
規範化的需求:
規範化設計強調資料的獨立性,資料應該儘可能少地冗餘,因為存在過多的冗餘資料,意味著要佔用了更多的物理空間,同時也對資料的維護和一致性檢查帶來了麻煩。
逆規範化的需求:
對於查詢操作很多的應用,一次查詢可能需要訪問多表進行,如果通過冗餘相同資料紀錄在一個表中,更新的代價增加不多,但是查詢操作效率可以有明顯提高。

(4)記憶體臨時表

使用create temporary table語法建立的臨時表,是基於連線的表,資料儲存在記憶體裡面,當連線斷開後,表會被刪除。
比如,當需要做統計分析時,如果統計的資料量不是很大,使用insert和select把資料移到臨時表中,比直接在表上做統計要效率更高。

(5)選擇合適的表型別

1)如果應用出現比較嚴重的鎖衝突,請考慮是否更改儲存引擎到innodb,行鎖機制可以減少鎖衝突。
2)如果應用查詢操作很多,且不需要事務型業務,則可以考慮使用Myisam儲存引擎。

3、優化客戶端應用

(1)使用連線池 

對於訪問資料庫來說,建立連線的代價比較昂貴,因此,我們有必要建立"連線池"以提高訪問的效能。
我們可以把連線當作物件或者裝置,池中又有許多已經建立的連線,訪問本來需要與資料庫的連線的地方,都改為和池相連,池臨時分配連線供訪問使用,結果返回後,訪問將連線交還。

(2)避免重複檢索

理清訪問邏輯,需要對相同表的訪問,儘量集中在相同sql訪問,一次提取結果,減少對資料庫的重複訪問。

4、優化資料庫伺服器

(1)使用mysql查詢快取

作用:
查詢快取會儲存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 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      查詢快取中的塊的總數目

(2)使用機器快取記憶體

Cache(快取記憶體)、Memory(記憶體)、Hard disk(硬碟)都是資料存取單元,但存取速度卻有很大差異,呈依次遞減的順序。
對於CPU來說,它可以從距離自己最近的Cache高速地存取資料,而不是從記憶體和硬碟以低幾個數量級的速度來存取資料。
而Cache中所儲存的資料,往往是CPU要反覆存取的資料,有特定的機制(或程式)來保證Cache內資料的命中率(Hit Rate)。
因此,CPU存取資料的速度在應用快取記憶體後得到了巨大的提高。

因為將資料寫入快取記憶體的任務由Cache Manager負責,所以對使用者來說快取記憶體的內容肯定是隻讀的。
需要你做的工作很少,程式中的SQL語句和直接訪問DBMS時沒有分別,返回的結果也看不出有什麼差別。而資料庫廠商往往會在DB Server的配置檔案中提供與Cache相關的引數,通過修改它們,可針對我們的應用優化Cache的管理。

(3)均衡負載

1)讀寫分流(主從複製)
利用mysql的主從複製可以有效的分流更新操作和查詢操作。
具體的實現是一個主伺服器,承擔更新操作(為了資料的一致性),多臺從伺服器,承擔查詢操作(多臺從伺服器一方面用來確保可用性,一方面可以建立不同的索引滿足不同查詢的需要),主從之間通過複製實現資料的同步。
主從複製優化:
對於主從之間不需要複製全部表的情況,可以通過在主的伺服器上搭建一個虛擬的從伺服器,將需要複製到從伺服器的表設定成blackhole引擎,然後定義replicate-do-table引數只複製這些表,這樣就過濾出需要複製的binlog,減少了傳輸binlog的頻寬。因為搭建的虛擬的從伺服器只起到過濾binlog的作用,並沒有實際紀錄任何資料,所以對主資料庫伺服器的效能影響也非常的有限。
注意:
通過複製分流查詢的存在的問題是主資料庫上更新頻繁或者網路出現問題的時候,主從之間的資料可能存在差異,造成查詢結果的異議,應用在設計的時候需要有所考慮。


2)分散式的資料庫
分散式的資料庫設計適合大資料量,負載高的情況,可平均多臺伺服器的負載,有良好的擴充套件性和高效性(讀寫效率)。
分散式事務:
mysql從5.0.3開始支援分散式事務,目前分散式事務只對Innodb儲存引擎支援。
也可以使用mysql的Cluster功能(NDB引擎)或者使用自己用mysql api來實現全域性事務。