二、優化數據庫對象
1、優化表的數據類型
應用設計的時候需要考慮字段的長度留有一定的冗余,但不推薦很多字段都留有大量的冗余,這樣既浪費磁盤空間,也在應用操作時浪費物理內存。
在MySQL中,可以使用函數PROCEDURE ANALYSE()對表進行分析,給出優化建議。(16, 256)是指不為包含的值多於16或者256字節的ENUM類型提出建議。

Optimal_fieldtype為優化建議,可以通過alter修改字段類型:ALTER TABLE TAB_NAME MODIFY COLUMN Optimal_fieldtype_VALUE;
2、逆規范化
反規划的好處是降低連接操作的需求、降低外碼和索引的數目、減少表的數目,帶來的問題可能會出現數據的完整性問題,雖然查詢加快,但是會降低修改速度。在進行反規范操作之前,要充分考慮數據的存取需求、常用表的大小、一些特殊的計算、數據的物理存儲位置。常用的反規范技術如下:
- 增加冗余列:指在多個表中具有相同的列,它常用來查詢時避免連接操作。
- 增加派生列:指增加的列來自其他表中的數據,由其他表中的數據經過計算生成,作用是減少連接操作,避免使用集函數。
- 重新組表:指如果許多用戶需要查看兩個表連接出來的結果數據,則把這兩個表重新組成一個表來減少連接而提高性能。
- 分庫分表:https://www.cnblogs.com/dukuan/p/9480610.html
3、使用中間表提高統計查詢速度
對於數據量較大的表,在其上進行統計查詢通常會效率很低,並且還會對線上應用產生負面影響。此種情況下可以使用中間表提高統計查詢的效率。
一般步驟:創建表結構和原表結構相同的表,遷移數據需要統計的數據,進行統計。
中間表在統計查詢中的優點:
- 中間表復制源表數據,並且與源表相“隔離”,在中間表上做統計查詢不會對線上應用產生負面影響。
- 中間表上可以靈活的添加索引或增加臨時用的新字段,從而達到提高統計查詢效率和輔助統計查詢作用。
三、鎖問題
1、鎖概述
MyISAM和MEMORY存儲引擎采用表級鎖,BDB存儲引擎(MySQL5.1后不直接支持此存儲引擎)采用的頁面鎖,也支持表級鎖,InnoDB支持行級鎖和表級鎖,默認是行級。
- 表級鎖:開銷小,加鎖快,不會出現死鎖,鎖定粒度大,發生鎖沖突的概率最高,並發度最低。
- 行級鎖:開銷大,加鎖慢,會出現死鎖,鎖定粒度小,發生沖突的概率最低,並發度也最高。
- 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間,會出現死鎖,鎖定粒度界於表鎖和行鎖之間,並發度一般。
表級鎖更適合以查詢為主,只有少量按索引條件更新數據的應用,如Web應用。而行級鎖更適合有大量按索引條件並發更新少量不同數據,同時又有並發查詢的應用,如在線事物處理系統。
2、MyISAM表鎖
MyISAM只支持表鎖。
2.1 查詢表級鎖爭用情況

Table_locks_immediate:產生表級鎖定的次數。
如果Table_locks_waited比較高,說明存在着嚴重的表級鎖爭用情況。
2.2 MySQL表級鎖的鎖模式
表級鎖分為表共享讀鎖和表獨占寫鎖。兼容性如下:

對於MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一個表的寫請求。對表的寫操作,會阻塞同一表的讀和寫。MyISAM讀操作和寫操作以及寫操作之間是串行的。
2.3 如何加鎖表
MyISAM在執行SELECT、UPDATE、DELETE、INSERT前,會自動給涉及的表加鎖,無需用戶干預。
顯式加鎖情況:比如同時要查詢或者比對兩個表中的內容,為防止在查其中一個時另一個有更新或者新數據,此時需要顯式的為兩個表加鎖。
加鎖命令:LOCK TABLES tab_name READ LOCAL, tab_name2 READ LOCAL; -- LOCAL表示允許其他用戶在MyISAM表尾並發插入記錄。使用顯式加鎖時,必須同時取得所有涉及表的鎖,而且加鎖后只能訪問加鎖的這些表,不能訪問其他表。並且如果加的是讀鎖,那么只能執行查詢操作。並且加鎖時需要對別名也要加鎖。
2.4 MyISAM並發插入
MyISAM通過concurrent_insert參數決定是否允許並發插入
- 0:不允許並發插入
- 1:MyISAM表中無空洞,允許讀的同時在表末尾插入記錄。默認設置
- 2:無論是否有無空洞,都能插入。
可以利用並發插入特性來解決應用中對同一表查詢和插入的鎖爭用。同時定期在系統空閑時整理空間碎片,收回因刪除記錄而產生的中間空洞。
注意:只能insert不能update和delete。且鎖表的session不能獲取到新插入到的數據。
2.5 MyISAM的鎖調度
同一時刻請求的寫鎖和讀鎖,MySQL會優先處理寫進程。即使是讀請求先到等待隊列,寫鎖也會插入到讀鎖請求之前,這也是MyISAM表不太適合於有大量更新操作和查詢操作應用的原因。
調節MyISAM的調度行為:
- 通過制定啟動參數low-priority-updates, 使MyISAM引擎默認給予讀請求以優先的權利。
- 通過執行命令 SET LOW_PRIORITY_UPDATES = 1,來降低更新請求的優先級。
- 通過制定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先級。
MySQL也提供了一種折中的辦法,即給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個值后,MySQL就暫時將寫請求的優先級降低。
3、InnoDB鎖問題
3.1 背景知識
3.1.1 事務及其ACID屬性
事務是由一組SQL語句組成的邏輯處理單元,具有以下4個屬性,通常簡稱為事務的ACID屬性。
- 原子性:事務是一個原子操作單元,其對數據的修改,要么全部執行,要么全都不執行。
- 一致性:在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構也都必須是正確的。
- 隔離性:數據庫系統提供一定的隔離機制,保證事務在不受外部並發操作影響的獨立環境執行。
- 持久性:事務完成之后,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。
3.1.2 並發事務處理的問題
相對於串行處理來說,並發事務處理能大大增加數據庫資源利用率,提供吞吐量,但會引起下列問題:
- 更新丟失:兩個或多個事務同時操作同一行,會覆蓋其他事務的更新。
- 臟讀:當一個事務在對一條記錄做修改未完成並提交前,另一個事務來讀取同一條記錄,這時讀取到的數據叫臟讀。
- 不可重復讀:一個事務在讀取某些數據后的某個時間,再次讀取,發現讀出的數據已經發生了改變或某些記錄已經被刪除了。
- 幻讀:一個事務按相同的查詢條件重新讀取以前檢索過的數據,其他事務插入了滿足其條件的新數據。
3.1.3 事務隔離級別
線上業務應完全避免更新丟失,但是避免此情況需要應用程序對要更新的數據加必要的鎖來解決。但是關於讀一致性,必須由數據庫提供一定的事務隔離機制來解決。數據庫事務隔離的方式,基本上可分為以下兩種:
- 在讀取數據之前,對其加鎖。
- 使用數據多版本並發控制(MVCC/MCC),按照請求時間點創建快照。
數據庫的事務隔離越嚴格,並發副作用越小,相應的代價也就越大,因為事務隔離實質是進行"串行化"。
為了解決隔離和並發的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別。應用可以根據自己的業務邏輯要求,選擇不同的隔離級別來平衡隔離和並發的矛盾。

3.2 獲取InnoDB行鎖爭用情況

如果Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比較高,則爭用比較嚴重。
此時可以通過查詢information_schema數據庫中的表來查看鎖情況,或者通過設置InnoDB Monitors來觀察發生鎖沖突的表、數據行等。
- 通過information_schema
SELECT * FROM innodb_locks;
SELECT * FROM innodb_lock_waits;
- 通過InnoDB Monitors
CREATE TABLE innodb_monitor(a INT) ENGINE = INNODB;
然后通過:SHOW ENGINE INNODB STATUS查看
關閉監視器:DROP TABLE innodb_monitor;
3.3 InnoDB行鎖模式及加鎖方法
InnoDB實現了兩種類型的行鎖:
- 共享鎖:允許另一個事務也獲得共享鎖,但是阻止其他事務獲得相同數據集的排他鎖。
- 排他鎖:允許獲得排他鎖的事務更新數據,阻止其他事務取得相同數據集的共享讀鎖和排他寫鎖。
事務獲取鎖的方式:
- 共享鎖:SELECT ... WHERE ... LOCK IN SHARE MODE;
- 排他鎖:SELECT ... WHERE ... FOR UPDATE;
對於鎖定行后需要進行更新操作的應用,用過使用排他鎖。
3.4 InnoDB行鎖實現方式
InnoDB行鎖是通過索引上的索引項加鎖來實現的,如果沒有索引,InnoDB將通過隱藏的聚簇索引來記錄加鎖。分為3種情形:
- Record lock:對索引項加鎖。
- Gap lock:對索引項之間的“間隙”、第一條記錄前的“間隙”或最后一條記錄后的“間隙”加鎖。
- Next-key lock:前兩種的組合,對記錄及其前面的間隙加鎖。
注意:如果不通過索引條件檢索數據,那么InnoDB將對表中的所有記錄加鎖,等同於表鎖,生產環境中需要注意這一特性防止導致大量的鎖沖突,從而影響並發性能。
由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然是訪問不同行的記錄,但是如果使用相同的索引建,會出現鎖沖突。
當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,但是當不同的session查詢相同的數據時,同樣會阻塞。
當表的數據較少,此時MySQL可能會全盤掃描,此時會導致不使用索引查詢,進而導致全表加鎖。
MySQL檢索的數據類型與索引字段不同,會進行數據轉換,但卻不會使用索引,所以會導致InnoDB對所有的記錄加鎖。
3.5 Next-Key鎖
當我們使用范圍查詢,並請求共享或排他鎖時,InnoDB會給符合條件的所有索引項加鎖,對於在范圍內但是不存在的記錄,叫做間隙GAP,同時也會被加鎖,這個鎖叫做Next-Key鎖。Next-Key鎖時為了防止幻讀。
當使用范圍檢索並鎖定記錄時,InnoDB會阻塞條件范圍內鍵值的並發插入,這往往會造成嚴重的鎖等待。因此,在實際應用開發中,尤其是並發插入比較多的應用,盡量使用相等條件來訪問更新數據,避免使用范圍條件。
3.6 什么時候使用行級鎖
對於InnoDB,絕大部分情況下都應該使用行級鎖,個別特殊事務中,可以考慮使用表級鎖。
- 事務需要更新大部分或全部數據,表比較大,如果使用默認的行鎖,會造成事務執行效率低,而且可能造成其他事務長時間鎖等待和鎖沖突。這種情況考慮使用表鎖來提高事務的執行速度。
- 事務設計多個表,比較復雜,很可能引起死鎖,造成大量事務回滾,此時可考慮使用表鎖避免死鎖,減少數據庫因事務回滾帶來的開銷。
當然,生產環境中最好不要出現這兩種事務,否則就應該考慮MyISAM了。
表鎖注意事項:
- 表鎖不是由InnoDB存儲引擎管理的,而是由MySQL Server負責的,僅當autocommit=0,innodb_table_locas=1(缺省值)時,InnoDB才能知道MySQL加的表鎖,也才能感知InnoDB加的行鎖, 這種情況下,InnoDB才能自動識別涉及表級鎖的死鎖,否則InnoDB將無法自動檢測並處理這種死鎖。
- 在用LOCK TABLES對InnoDB加鎖時,需要將AUTOCOMMIT設為0,否則不會給表加鎖;事務結束前,不要用UNLOCKS TABLES釋放表鎖,因為UNLOCK TABLES會隱含提交事務;COMMIT或ROLLBACK並不能釋放用LOCK TABLES加的表級鎖,必須用UNLOCK TABLES釋放表鎖。
方式如下:寫表t1並從表t2讀
SET AUTOCOMMIT = 0;
LOCK TABLES t1 WRITE, t2 READ;
[do something..]
COMMIT;
UNLOCK TABLES;
3.7 關於死鎖
MyISAM表鎖是一次獲得所需全部鎖,要么全部滿足,要么等待,因為不會出現死鎖。但InnoDB鎖是逐步獲得的,所以InnoDB會發生死鎖的可能。
比如:session1正在對table_1進行 select for update(獲得tb1排他鎖) ,此時session2對table_2進行select for update(獲得tb2排他鎖),如果此時session1對tb2進行select for update,會出現等待,直到session2釋放,但是如果session2沒有釋放並且又請求tb1進行select for update,那么此時會出現死鎖。
發生死鎖后,InnoDB一般都能自動檢測到,然后釋放一個事務鎖並回退,另一個事務獲得鎖繼續完成事務。但在涉及外部鎖或涉及鎖的情況下,InnoDB並不能完全自動檢測到死鎖,這需要通過設置鎖超時參數innodb_lock_wait_timeout來解決,但這個參數並不是用來解決死鎖問題,在並發訪問比較高的情況下,如果大量事務因無法立即獲得所需的鎖而掛起,會拖垮數據庫,設置合適的值可以避免或者減少此種情況的發生。
通常來說,死鎖都是應用設計的問題,通過調整業務流程、數據庫對象設計、事務大小,以及訪問數據庫的SQL語句,絕大部分死鎖都可以避免。
避免死鎖的常用方法:
- 程序並發存取多個表,盡量約定相同的順序來訪問量。
- 程序批量處理數據的時候,事先對數據排序,保證每個線程按固定的順序來處理記錄。
- 在事務中,如果要更新記錄,應該直接申請足夠級別的排他鎖,而不是先申請共享鎖,后申請排他鎖。要不然其他事務可能已經獲得了相同的共享鎖,從而造成鎖沖突。
- 在REPEATABLE-READ隔離級別下,如果兩個線程同時對相同條件記錄用SELECT FOR UPDATE加排他鎖,在沒有符合條件的記錄的情況下,兩個線程都會加鎖成功。因為兩個線程查詢均無此記錄,便會嘗試插入一條新紀錄,如果兩個線程都這么做,就會出現死鎖。這種情況將隔離級別改成READ COMMITTED就可避免。
