實踐中如何優化MySQL
實踐中,MySQL的優化主要涉及SQL語句及索引的優化、數據表結構的優化、系統配置的優化和硬件的優化四個方面,如下圖所示:
SQL語句及索引的優化
SQL語句的優化
SQL語句的優化主要包括三個問題,即如何發現有問題的SQL、如何分析SQL的執行計劃以及如何優化SQL,下面將逐一解釋。
怎么發現有問題的SQL?(通過MySQL慢查詢日志對有效率問題的SQL進行監控)
MySQL的慢查詢日志是MySQL提供的一種日志記錄,它用來記錄在MySQL中響應時間超過閥值的語句,具體指運行時間超過long_query_time值的SQL,則會被記錄到慢查詢日志中。
long_query_time的默認值為10,意思是運行10s以上的語句。慢查詢日志的相關參數如下所示:
通過MySQL的慢查詢日志,我們可以查詢出執行的次數多占用的時間長的SQL、可以通過pt_query_disgest(一種mysql慢日志分析工具)分析Rows examine(MySQL執行器需要檢查的行數)項去找出IO大的SQL以及發現未命中索引的SQL,對于這些SQL,都是我們優化的對象。
通過explain查詢和分析SQL的執行計劃
使用 EXPLAIN 關鍵字可以知道MySQL是如何處理你的SQL語句的,以便分析查詢語句或是表結構的性能瓶頸。通過explain命令可以得到表的讀取順序、數據讀取操作的操作類型、哪些索引可以使用、哪些索引被實際使用、表之間的引用以及每張表有多少行被優化器查詢等問題。當擴展列extra出現Using filesort和Using temporay,則往往表示SQL需要優化了。
優化SQL語句
優化insert語句:一次插入多值;
應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描;
應盡量避免在 where 子句中對字段進行null值判斷,否則將導致引擎放棄使用索引而進行全表掃描;
優化嵌套查詢:子查詢可以被更有效率的連接(Join)替代;
很多時候用 exists 代替 in 是一個好的選擇。
索引優化
建議在經常作查詢選擇的字段、經常作表連接的字段以及經常出現在order by、group by、distinct 后面的字段中建立索引。但必須注意以下幾種可能會引起索引失效的情形:
以“%(表示任意0個或多個字符)”開頭的LIKE語句,模糊匹配;
OR語句前后沒有同時使用索引;
數據類型出現隱式轉化(如varchar不加單引號的話可能會自動轉換為int型);
對于多列索引,必須滿足最左匹配原則(eg,多列索引col1、col2和col3,則 索引生效的情形包括col1或col1,col2或col1,col2,col3)。
數據庫表結構的優化
數據庫表結構的優化包括選擇合適數據類型、表的范式的優化、表的垂直拆分和表的水平拆分等手段。
選擇合適數據類型
使用較小的數據類型解決問題;
使用簡單的數據類型(mysql處理int要比varchar容易);
盡可能的使用not null 定義字段;
盡量避免使用text類型,非用不可時最好考慮分表;
表的范式的優化
一般情況下,表的設計應該遵循三大范式。
表的垂直拆分
把含有多個列的表拆分成多個表,解決表寬度問題,具體包括以下幾種拆分手段:
把不常用的字段單獨放在同一個表中;
把大字段獨立放入一個表中;
把經常使用的字段放在一起;
這樣做的好處是非常明顯的,具體包括:拆分后業務清晰,拆分規則明確、系統之間整合或擴展容易、數據維護簡單。
表的水平拆分
表的水平拆分用于解決數據表中數據過大的問題,水平拆分每一個表的結構都是完全一致的。一般地,將數據平分到N張表中的常用方法包括以下兩種:
對ID進行hash運算,如果要拆分成5個表,mod(id,5)取出0~4個值;
針對不同的hashID將數據存入不同的表中;
表的水平拆分會帶來一些問題和挑戰,包括跨分區表的數據查詢、統計及后臺報表的操作等問題,但也帶來了一些切實的好處:
表分割后可以降低在查詢時需要讀的數據和索引的頁數,同時也降低了索引的層數,提高查詢速度;
表中的數據本來就有獨立性,例如表中分別記錄各個地區的數據或不同時期的數據,特別是有些數據常用,而另外一些數據不常用。
需要把數據存放到多個數據庫中,提高系統的總體可用性(分庫,雞蛋不能放在同一個籃子里)。
系統配置的優化
操作系統配置的優化:增加TCP支持的隊列數
mysql配置文件優化:Innodb緩存池設置(innodb_buffer_pool_size,推薦總內存的75%)和緩存池的個數(innodb_buffer_pool_instances)
硬件的優化
CPU:核心數多并且主頻高的 內存:增大內存 磁盤配置和選擇:磁盤性能
MySQL中的悲觀鎖與樂觀鎖的實現
悲觀鎖與樂觀鎖是兩種常見的資源并發鎖設計思路,也是并發編程中一個非常基礎的概念。
悲觀鎖
悲觀鎖的特點是先獲取鎖,再進行業務操作,即“悲觀”的認為所有的操作均會導致并發安全問題,因此要先確保獲取鎖成功再進行業務操作。通常來講,在數據庫上的悲觀鎖需要數據庫本身提供支持,即通過常用的select … for update操作來實現悲觀鎖。當數據庫執行select … for update時會獲取被select中的數據行的行鎖,因此其他并發執行的select … for update如果試圖選中同一行則會發生排斥(需要等待行鎖被釋放),因此達到鎖的效果。select for update獲取的行鎖會在當前事務結束時自動釋放,因此必須在事務中使用。 這里需要特別注意的是,不同的數據庫對select… for update的實現和支持都是有所區別的,例如oracle支持select for update no wait,表示如果拿不到鎖立刻報錯,而不是等待,mysql就沒有no wait這個選項。另外,mysql還有個問題是: select… for update語句執行中所有掃描過的行都會被鎖上,這一點很容易造成問題。因此,如果在mysql中用悲觀鎖務必要確定使用了索引,而不是全表掃描。
樂觀鎖
樂觀鎖的特點先進行業務操作,只在最后實際更新數據時進行檢查數據是否被更新過,若未被更新過,則更新成功;否則,失敗重試。樂觀鎖在數據庫上的實現完全是邏輯的,不需要數據庫提供特殊的支持。一般的做法是在需要鎖的數據上增加一個版本號或者時間戳,然后按照如下方式實現:
SELECT data AS old_data, version AS old_version FROM …;
//根據獲取的數據進行業務操作,得到new_data和new_version
UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 樂觀鎖獲取成功,操作完成
} else {
// 樂觀鎖獲取失敗,回滾并重試
}
樂觀鎖是否在事務中其實都是無所謂的,其底層機制是這樣:在數據庫內部update同一行的時候是不允許并發的,即數據庫每次執行一條update語句時會獲取被update行的寫鎖,直到這一行被成功更新后才釋放。因此在業務操作進行前獲取需要鎖的數據的當前版本號,然后實際更新數據時再次對比版本號確認與之前獲取的相同,并更新版本號,即可確認這其間沒有發生并發的修改。如果更新失敗,即可認為老版本的數據已經被并發修改掉而不存在了,此時認為獲取鎖失敗,需要回滾整個業務操作并可根據需要重試整個過程。
悲觀鎖與樂觀鎖的應用場景
一般情況下,讀多寫少更適合用樂觀鎖,讀少寫多更適合用悲觀鎖。樂觀鎖在不發生取鎖失敗的情況下開銷比悲觀鎖小,但是一旦發生失敗回滾開銷則比較大,因此適合用在取鎖失敗概率比較小的場景,可以提升系統并發性能。
MySQL存儲引擎中的MyISAM和InnoDB區別詳解
在MySQL 5.5之前,MyISAM是mysql的默認數據庫引擎,其由早期的ISAM(Indexed Sequential Access Method:有索引的順序訪問方法)所改良。雖然MyISAM性能極佳,但卻有一個顯著的缺點: 不支持事務處理。不過,MySQL也導入了另一種數據庫引擎InnoDB,以強化參考完整性與并發違規處理機制,后來就逐漸取代MyISAM。
InnoDB是MySQL的數據庫引擎之一,其由Innobase oy公司所開發,2006年五月由甲骨文公司并購。與傳統的ISAM、MyISAM相比,InnoDB的最大特色就是支持ACID兼容的事務功能,類似于PostgreSQL。目前InnoDB采用雙軌制授權,一是GPL授權,另一是專有軟件授權。具體地,MyISAM與InnoDB作為MySQL的兩大存儲引擎的差異主要包括:
存儲結構:每個MyISAM在磁盤上存儲成三個文件:第一個文件的名字以表的名字開始,擴展名指出文件類型。.frm文件存儲表定義,數據文件的擴展名為.MYD (MYData),索引文件的擴展名是.MYI (MYIndex)。InnoDB所有的表都保存在同一個數據文件中(也可能是多個文件,或者是獨立的表空間文件),InnoDB表的大小只受限于操作系統文件的大小,一般為2GB。
存儲空間:MyISAM可被壓縮,占據的存儲空間較小,支持靜態表、動態表、壓縮表三種不同的存儲格式。InnoDB需要更多的內存和存儲,它會在主內存中建立其專用的緩沖池用于高速緩沖數據和索引。
可移植性、備份及恢復:MyISAM的數據是以文件的形式存儲,所以在跨平臺的數據轉移中會很方便,同時在備份和恢復時也可單獨針對某個表進行操作。InnoDB免費的方案可以是拷貝數據文件、備份 binlog,或者用 mysqldump,在數據量達到幾十G的時候就相對痛苦了。
事務支持:MyISAM強調的是性能,每次查詢具有原子性,其執行數度比InnoDB類型更快,但是不提供事務支持。InnoDB提供事務、外鍵等高級數據庫功能,具有事務提交、回滾和崩潰修復能力。
AUTO_INCREMENT:在MyISAM中,可以和其他字段一起建立聯合索引。引擎的自動增長列必須是索引,如果是組合索引,自動增長可以不是第一列,它可以根據前面幾列進行排序后遞增。InnoDB中必須包含只有該字段的索引,并且引擎的自動增長列必須是索引,如果是組合索引也必須是組合索引的第一列。
表鎖差異:MyISAM只支持表級鎖,用戶在操作MyISAM表時,select、update、delete和insert語句都會給表自動加鎖,如果加鎖以后的表滿足insert并發的情況下,可以在表的尾部插入新的數據。InnoDB支持事務和行級鎖。行鎖大幅度提高了多用戶并發操作的新能,但是InnoDB的行鎖,只是在WHERE的主鍵是有效的,非主鍵的WHERE都會鎖全表的。
全文索引:MyISAM支持 FULLTEXT類型的全文索引;InnoDB不支持FULLTEXT類型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。
表主鍵:MyISAM允許沒有任何索引和主鍵的表存在,索引都是保存行的地址。對于InnoDB,如果沒有設定主鍵或者非空唯一索引,就會自動生成一個6字節的主鍵(用戶不可見),數據是主索引的一部分,附加索引保存的是主索引的值。
表的具體行數:MyISAM保存表的總行數,select count() from table;會直接取出出該值;而InnoDB沒有保存表的總行數,如果使用select count() from table;就會遍歷整個表,消耗相當大,但是在加了wehre條件后,myisam和innodb處理的方式都一樣。
CURD操作:在MyISAM中,如果執行大量的SELECT,MyISAM是更好的選擇。對于InnoDB,如果你的數據執行大量的INSERT或UPDATE,出于性能方面的考慮,應該使用InnoDB表。DELETE從性能上InnoDB更優,但DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除,在innodb上如果要清空保存有大量數據的表,最好使用truncate table這個命令。
外鍵:MyISAM不支持外鍵,而InnoDB支持外鍵。
通過上述的分析,基本上可以考慮使用InnoDB來替代MyISAM引擎了,原因是InnoDB自身很多良好的特點,比如事務支持、存儲過程、視圖、行級鎖、外鍵等等。尤其在并發很多的情況下,相信InnoDB的表現肯定要比MyISAM強很多。另外,必須需要注意的是,任何一種表都不是萬能的,合適的才是最好的,才能最大的發揮MySQL的性能優勢。如果是不復雜的、非關鍵的Web應用,還是可以繼續考慮MyISAM的,這個具體情況具體考慮。
MyISAM:不支持事務,不支持外鍵,表鎖;插入數據時鎖定整個表,查行數時無需整表掃描。主索引數據文件和索引文件分離;與主索引無區別;
InnoDB:支持事務,外鍵,行鎖,查表總行數時,全表掃描;主索引的數據文件本身就是索引文件;輔助索引記錄主鍵的值;
MySQL鎖類型
根據鎖的類型分,可以分為共享鎖,排他鎖,意向共享鎖和意向排他鎖。
根據鎖的粒度分,又可以分為行鎖,表鎖。
對于mysql而言,事務機制更多是靠底層的存儲引擎來實現,因此,mysql層面只有表鎖,而支持事務的innodb存 儲引擎則實現了行鎖(記錄鎖(在行相應的索引記錄上的鎖)),gap鎖(是在索引記錄間歇上的鎖),next-key鎖(是記錄鎖和在此索引記錄之前的gap上的鎖的結合)。Mysql的記錄鎖實質是索引記錄的鎖,因為innodb是索引組織表;gap鎖是索引記錄間隙的鎖,這種鎖只在RR隔離級別下有效;next-key鎖是記錄鎖加上記錄之前gap鎖的組合。mysql通過gap鎖和next-key鎖實現RR隔離級別。
說明:對于更新操作(讀不上鎖),只有走索引才可能上行鎖;否則會對聚簇索引的每一行上寫鎖,實際等同于對表上寫鎖。 若多個物理記錄對應同一個索引,若同時訪問,也會出現鎖沖突;
當表有多個索引時,不同事務可以用不同的索引鎖住不同的行,另外innodb會同時用行鎖對數據記錄(聚簇索引)加鎖。
MVCC(多版本并發控制)并發控制機制下,任何操作都不會阻塞讀操作,讀操作也不會阻塞任何操作,只因為讀不上鎖。 共享鎖:由讀表操作加上的鎖,加鎖后其他用戶只能獲取該表或行的共享鎖,不能獲取排它鎖,也就是說只能讀不能寫
排它鎖:由寫表操作加上的鎖,加鎖后其他用戶不能獲取該表或行的任何鎖,典型是mysql事務中的更新操作。
意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
數據庫死鎖概念
多數情況下,可以認為如果一個資源被鎖定,它總會在以后某個時間被釋放。而死鎖發生在當多個進程訪問同一數據庫時,其中每個進程擁有的鎖都是其他進程所需的,由此造成每個進程都無法繼續下去。簡單的說,進程A等待進程B釋放他的資源,B又等待A釋放他的資源,這樣就互相等待就形成死鎖。
雖然進程在運行過程中,可能發生死鎖,但死鎖的發生也必須具備一定的條件,死鎖的發生必須具備以下四個必要條件:
1)互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程占用。如果此時還有其它進程請求資源,則請求者只能等待,直至占有資源的進程用畢釋放。 2)請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程占有,此時請求進程阻塞,但又對自己已獲得的其它資源保持不放。 3)不剝奪條件:指進程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。 4)環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,???,Pn}中的P0正在等待一個P1占用的資源;P1正在等待P2占用的資源,……,Pn正在等待已被P0占用的資源。 下列方法有助于最大限度地降低死鎖:
按同一順序訪問對象。
避免事務中的用戶交互。
保持事務簡短并在一個批處理中。
使用低隔離級別。
使用綁定連接。