MySQL設計規範與性能優化

【轉自:http://www.cnblogs.com/pflee/p/4592647.html

引言

MySQL是目前使用最爲廣泛的關係型數據庫之一,如果使用得當,可支撐企業級高併發、高可靠服務,使用不當甚至連併發量略高的個人網站都難以支撐;

就算使用了緩存,大量的數據庫訪問依舊在所難免,即使設置了較長的緩存有效期,而且緩存命中率較理想,但緩存的創建過期後的重建都是需要訪問數據庫的; 

本文主要從MySQL表結構設計規範MySQL自身性能優化兩方面來討論該如何對MySQL數據庫進行優化;

MySQL表結構設計規範

1. 數據庫設計命名規範

(1)數據庫,數據表一律使用前綴,前綴名稱一般不超過5字;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 正式數據庫名使用小寫英文以及下劃線組成,儘量說明是哪個應用或者系統在使用的;
mcs_webim
ops_portal
  
# 備份數據庫名使用正式庫名加上備份時間組成;
ops_portal_20150621
mcs_webim_20150622
  
# 相關應用的數據表使用同一前綴,前綴名稱一般不超過5字;
webim_user
webim_group
  
# 備份數據表名使用正式表名加上備份時間組成;
webim_user_20150620
webim_group_20150620

(2)字段名稱使用單詞組合完成,首字母小寫,後面單詞的首字母大寫(駝峯式),最好是帶表名前綴;

1
2
3
4
5
6
# 如表webim_user的字段
userId
userName
userPassword
# 表與表之間的相關聯字段要用統一名稱;
# 用盡量少的存儲空間來存儲一個字段的數據;

2. 數據庫規範化設計

(1)範式化設計

實際關係模式設計中,一般遵循第三範式——在一個數據表中,非主鍵字段之間不能存在依賴關係;

具體可參考:規範化—數據庫設計原則

(2)反範式化設計

舉例:在頁面顯示我的好友列表;
1.遵循第三範式

1
2
(用戶ID, 好友ID)
(用戶ID, 用戶暱稱, 用戶郵箱, 註冊時間, 聯繫電話)

 

2.反範式化設計

1
2
(用戶ID, 好友ID, 好友暱稱)
(用戶ID, 用戶暱稱, 用戶郵箱, 註冊時間, 聯繫電話)

 

反範式化問題:
1. 數據冗餘;
2. 更新導致數據不一致問題,可通過定期同步的手段來修改不一致數據;

反範式化優勢:
減少讀取數據的開銷,這點非常重要,需要根據不同場景來適當使用反範式化設計;

 MySQL自身性能優化

MySQL自身優化主要從如下幾方面來介紹:

1. Query語句優化;
2. 慢查詢優化;
3. MySQL鎖機制分析優化;
4. 參數配置優化;

Query語句優化

1. MySQL狀態報告

1
2
show status;
show engine innodb status;

一些更加友好的第三方工具:mysqlreport下載地址),mysqltunnermytop等,可展示更加友好的狀態報告;

2. 正確使用索引

如果索引使用不當,其他任何優化將毫無意義;

索引目的

索引類似於書的目錄,通過不斷的縮小想要獲得數據的範圍來篩選出最終想要的結果,加快查詢速度;

索引類型

索引分單列索引和組合索引,組合索引的一個索引包含多列;
MySQL索引類型包括:

(1)普通索引——沒有任何限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 直接創建索引
create index indexName on tableName(columnName(length));
# 如果是charvarchar類型,length長度可以小於字段實際長度;
# 如果是blob和text類型,必須指定length;
 
# 修改表結構
alter tableName add index indexName on (columnName(length));
 
# 創建表時直接指定
create table tableName(
    id int not null,
    username varchar(16) not null,
    index indexName (columnName(length))
);
 
# 刪除索引
drop index indexName on tableName;
 
# 查看索引
show index from tableName;

(2唯一索引——索引列的值必須唯一,允許有空值,如果是組合索引,則列值的組合必須唯一;

1
2
3
4
5
6
7
8
9
10
11
12
# 直接創建索引
create unique index indexName on tableName(columnName(length));
 
# 修改表結構
alter tableName add unique indexName on (columnName(length));
 
# 創建表時直接指定
create table tableName(
    id int not null,
    username varchar(16) not null,
    unique indexName columnName(length)
);

(3)主鍵索引——是唯一索引的一種,但不允許有空值,一般是建表的時候直接創建主鍵索引;

1
2
3
4
5
create table tableName(
    id int not null,
    username varchar(16) not null,
    primary key(id)
);

(4)組合索引——爲多列添加索引;

1
2
3
4
# 假設數據表中name, age, address, zip等多個字段,需要爲name, age, zip建立組合索引;
create index indexName on tableName(name(10), age, zip);
# 或
alter table tableName add index indexName (name(10), age, zip);

對於varchar類型字段,如果長度過長,最好限制一下索引的長度,可以加快索引查詢速度,減少索引文件的大小;

最左前綴匹配

如上面的name,age,zip的組合索引,如下的組合都會用到該索引,可使用explain來進行分析:

1
2
3
4
5
6
7
8
9
10
11
select from tableName where name="lee" and age=20 and zip="050000";
select from tableName where name="lee" and age=20;
select from tableName where name="lee";
 
# 組合索引對於包含order bygroup by的查詢也可發揮作用,同樣遵循最左前綴原則(對於Hash索引,對order by無效);
select from tableName order by name, age, zip;
select from tableName where name="lee" order by age, zip;
 
# 對於group by,一般需要先將記錄分組後放在新的臨時表中,然後分別進行函數運算,如count(),sum(),max()等;
# 如有恰當的索引,可使用索引來取代創建臨時表;
select count(id) from tableName where sex='m' group by age, zip;

查詢優化器會自動調整條件表達式的順序,以匹配組合索引;
建立索引時一定要注意順序,(key1, key2)和(key2, key1)完全不同;

建立索引的時機

一般來說,在wherejoin中出現的列需要建立索引,mysql只對<<==>>=betweenin以及某些時候(不以通配符%和_開頭的查詢)的like纔會使用索引;

1
2
select a.name from table1 as left join table2 b on a.name=b.username where b.age=20 and b.zip='053000';
# 此時,需要對username,age和zip建立索引;

索引的不足之處

索引有很大優勢,但是不能濫用,需要根據實際情況來決定到底使不使用索引,該爲哪些字段建索引,一般在查詢量佔比較多的表纔會建立索引;

  • 索引會降低更新表的速度,如insert,update,delete操作,更新表時不僅需要保存數據,還要保存索引文件;
  • 過多的組合索引會大大加劇索引文件的膨脹速度,引起磁盤空間存儲的問題,一個包含多個字段的組合索引的尺寸可能已經超過了數據本身,而且索引過多,可也能會使MySQL選擇不到要使用的最好的索引(可使用use index(key_list)來指定查詢時使用的索引);
  • 對於唯一值的列,索引效果最好,對於具有多個重複值的列,如年齡或性別,建立索引不是好辦法;

使用索引注意事項

  1. 索引不會包含有NULL值的列,在數據庫設計時儘量不要讓字段的默認值爲NULL,否則無法建立相關字段的索引;
  2. 使用短索引,對varchar類型字段建索引時最好指定長度,只要保證前n個字符多數值是唯一的即可,提高查詢速度,節省磁盤空間,降低I/O操作;
  3. MySQL查詢只是用一個索引,因此如果一條查詢語句中有多個字段需要建索引,最好按照最左前綴匹配原則建立組合索引;
  4. like語句一般不鼓勵使用,在數據量大的情況下,非常容易造成性能問題,如果非使用,通配符%一定要放到後面,如like "abc%";
  5. 不要在列上進行運算,如select * from users where YEAR(datetime)<2015;,會導致索引失效,進行全表掃描;
  6. 不要使用NOT IN和IN;

索引的原理

主要參考:MySQL索引原理及慢查詢優化

3. join語句優化

join語句分爲內連接和外連接;

內連接:

1
2
3
select from inner join on a.id = b.id;
# 等價於
select from a,b where a.id = b.id;

內連接就是檢索出與連接條件完全匹配的數據行;

外連接:

1
select id, nameaction from user as left join user_action a on u.id = a.user_id;

外連接保留了所有驅動表的數據,匹配表中無法匹配的數據則以NULL輸出;

外連接工作原理

從左表讀取一條記錄,選出所有與on中條件匹配的右表記錄的(n條)數據,進行連接,形成n條記錄(包括重複的行),如果右邊沒有與on條件匹配的記錄,那連接的字段都是null,繼續讀下一條;

找出所有在左表而不在右表中的記錄:

1
2
3
4
5
6
7
# 注意:a.user_id必須聲明爲NOT NULL,如果a,u兩表連接條件中的兩個列具有相同的名字,可使用using(col);
select id, nameaction from user as u
    left join user_action a on u.id = a.user_id
    where a.user_id is NULL;
# 查詢時手動指定索引
select from table1 use index (key1, key2) where key1=1 and key2=2 and key3=3;
select from table1 ignore index (key3) where key1=1 and key2=2 and key3=3;

慢查詢優化

開啓慢查詢日誌:

1
2
3
# 在my.cnf中增加如下配置:
long_query_time = 1
log-slow-queries = /var/log/mysql/mysql_slow.log
# 將所有沒有使用索引的查詢記錄也記錄下來(根據需要決定是否開啓): log-queries-not-using-indexes

慢查詢工具mysqlsla,可使用此工具對慢查詢日誌進行分析;

1
# mysqlsla -lt slow /var/log/mysql/mysql_slow.log

大多數慢查詢都是因爲索引使用不當造成的,使用索引時一定要謹慎,其他原因還有查詢語句本身太過複雜(多表聯合查詢)數據表記錄數太多等;

鎖機制分析與優化

鎖機制是影響查詢性能的另一個重要因素;

查詢的時間開銷主要包括兩部分:

1. 查詢本身的計算時間;(主要受索引影響) 
2. 查詢開始前的等待時間;(受鎖機制影響)

減少表鎖定等待

MyISAM類型表提供了表級別鎖定,可使用mysqlreport來查看等待表鎖定查詢所佔的比例;

MyISAM的表鎖定允許多線程同時讀取數據,如select查詢,無需鎖等待;

對於更新操作,如update、insert、delete操作,會排斥對當前表的所有查詢,並且更新操作有着默認的更高優先級,即當表鎖釋放後,更新操作將先獲得鎖定,全部執行完畢後,才輪到讀取操作,應儘量避免在有大量查詢請求時,批量更改數據表,否則非常容易造成慢查詢;

可使用如下命令監視所有線程的狀態:

1
show processlist\G;

結論:

1
2
對於以查詢操作爲主,並且更新操作耗時較低的應用,將不會存在太多的鎖等待,可以使用MyISAM存儲引擎;
對於有頻繁數據更新並且查詢請求量也不低的站點,必須使用提供行鎖定功能的Innodb存儲引擎;

行鎖定

Innodb存儲引擎提供了行鎖定的支持;

行鎖定優勢:在select和update混合的情況下,行鎖定可以解決讀和寫互斥的問題,由於update操作和select操作來自不同的線程,並且針對的是不同行的記錄,可以併發進行;

行鎖定並不一定總是好的:

1. 行鎖定的開銷並不比表鎖定小;

2. 在全部都是更新操作的場景下,行鎖定耗時可能會更長,雖然表鎖定每次只有一個線程處於Updating狀態,而行鎖定所有線程都是Updating狀態,但鎖定只是一種邏輯層面的約束,即使全部線程都是Updating狀態,但是磁盤的物理寫操作還是串行執行的;

3. 對於全部查詢的場景,行鎖定也需要更多額外的開銷,速度相對錶鎖定略慢;

存儲引擎查看

1
2
show table status from DataBaseName where name='TableName';
alter table tableName type=myisam;

參數配置優化

事務性表性能優化

Innodb存儲引擎除了支持行鎖定,外鍵以及其易於修復的特性,另一個優勢就是其支持事務(ACID),當然,事實上大多數站點都不需要事務級別的保障;
Innodb是通過預寫日誌(WAL)方式來實現事務的,即當有事務提交時,首先寫入內存中的事務日誌緩衝區,隨後當事務日誌寫入磁盤時,Innodb才更新實際的數據和索引;
如果選擇使用事務,那事務日誌何時寫入磁盤,就是一個優化點了;

1
2
3
4
5
6
7
8
9
10
# 事務提交時立即將事務日誌寫入磁盤,數據和索引也立即更新,符合持久性原則;
innodb_flush_log_at_trx_commit = 1
 
# 事務提交時不立即寫入磁盤,每隔1S寫入磁盤文件一次,並刷新到磁盤,同時更新數據和索引;
# 如果mysql崩潰,事務日誌緩衝區中最近1秒內的數據永久性丟失;
innodb_flush_log_at_trx_commit = 0
 
# 事務提交時立即寫入磁盤文件,但間隔1S纔會刷新磁盤,同時更新數據和索引;
# 操作系統崩潰纔會造成數據損失;
innodb_flush_log_at_trx_commit = 2

注意:

“寫入磁盤文件”只是將數據寫入位於物理內存中的內核緩衝區,“刷新到磁盤“是將內核緩衝區中的數據真正寫入到磁盤;

innodb_flush_log_at_trx_commit設置爲0,可以獲得最好的性能,同時數據丟失的可能性也最大;如果希望儘量避免數據丟失,可設置爲2;

1
2
# 設置Innodb數據和索引的內存緩衝池大小,一般可設置爲服務器物理內存的80%;
innodb_buffer_pool_size = 12G

使用查詢緩存

目的:將select的查詢結果緩存在內存中,以供下次直接獲取;

1
2
3
query_cache_type = 1
query_cache_size = 64M
query_cache_limit = 1M

對於緩存過期策略,MySQL採用的機制是:當一個表有更新操作後,涉及這個表的所有查詢緩存都會失效;

這個看場景,對於密集select操作且很少更新的表,比較適合使用查詢緩存;對於select和update混合的應用,不適合使用查詢緩存;

臨時表

目的:在磁盤上創建臨時表非常耗時,開銷大,需要降低在磁盤上創建臨時表的次數;

1
2
# 儘量給臨時表設置較大的內存空間,當內存空間不夠時,MySQL將會啓用磁盤來保存臨時表;
tmp_table_size = 512M

線程池

MySQL採用多線程來處理併發連接,如果每次都新建連接,都要創建新的線程,在系統繁忙的時候,也會增加MySQL的開銷;

1
2
3
# 儘量使用持久連接,減少線程的重複創建;
thread_cache_size = 100
# 可以使MySQL緩存100個線程;

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章