悲觀鎖與樂觀鎖:mysql
悲觀鎖:顧名思義,就是很悲觀,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了不少這種鎖機制,好比行鎖,表鎖等,讀鎖,寫鎖等,都是在作操做以前先上鎖。react
樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,像數據庫若是提供相似於write_condition機制的其實都是提供的樂觀鎖。sql
表級:引擎 MyISAM,直接鎖定整張表,在你鎖按期間,其它進程沒法對該表進行寫操做。若是你是寫鎖,則其它進程則讀也不容許數據庫
頁級:引擎 BDB,表級鎖速度快,但衝突多,行級衝突少,但速度慢。因此取了折衷的頁級,一次鎖定相鄰的一組記錄緩存
行級:引擎 INNODB, 僅對指定的記錄進行加鎖,這樣其它進程仍是能夠對同一個表中的其它記錄進行操做。性能優化
上述三種鎖的特性可大體概括以下:session
1) 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低。併發
2) 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。oracle
3) 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。性能
三種鎖各有各的特色,若僅從鎖的角度來講,表級鎖更適合於以查詢爲主,只有少許按索引條件更新數據的應用,如WEB應用;行級鎖更適合於有大量按索引條件併發更新少許不一樣數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統。
MySQL表級鎖有兩種模式:
一、表共享讀鎖(Table Read Lock)。對MyISAM表進行讀操做時,它不會阻塞其餘用戶對同一表的讀請求,但會阻塞 對同一表的寫操做;
二、表獨佔寫鎖(Table Write Lock)。對MyISAM表的寫操做,則會阻塞其餘用戶對同一表的讀和寫操做。
MyISAM表的讀和寫是串行的,即在進行讀操做時不能進行寫操做,反之也是同樣。但在必定條件下MyISAM表也支持查詢和插入的操做的併發進行,其機制是經過控制一個系統變量(concurrent_insert)來進行的,當其值設置爲0時,不容許併發插入;當其值設置爲1時,若是MyISAM表中沒有空洞(即表中沒有被刪除的行),MyISAM容許在一個進程讀表的同時,另外一個進程從表尾插入記錄;當其值設置爲2時,不管MyISAM表中有沒有空洞,都容許在表尾併發插入記錄。
MyISAM鎖調度是如何實現的呢,這也是一個很關鍵的問題。例如,當一個進程請求某個MyISAM表的讀鎖,同時另外一個進程也請求同一表的寫鎖,此時mysql將會如優先處理進程呢?經過研究代表,寫進程將先得到鎖(即便讀請求先到鎖等待隊列)。但這也形成一個很大的缺陷,即大量的寫操做會形成查詢操做很難得到讀鎖,從而可能形成永遠阻塞。所幸咱們能夠經過一些設置來調節MyISAM的調度行爲。咱們可經過指定參數low-priority-updates,使MyISAM默認引擎給予讀請求以優先的權利,設置其值爲1(set low_priority_updates=1),使優先級下降。
InnoDB鎖與MyISAM鎖的最大不一樣在於:
一、是支持事務(TRANCSACTION)。
二、是採用了行級鎖。
咱們知道事務是由一組SQL語句組成的邏輯處理單元,其有四個屬性(簡稱ACID屬性),分別爲:
原子性(Atomicity):事務是一個原子操做單元,其對數據的修改,要麼所有執行,要麼全都不執行;
一致性(Consistent):在事務開始和完成時,數據都必須保持一致狀態;
隔離性(Isolation):數據庫系統提供必定的隔離機制,保證事務在不受外部併發操做影響的「獨立」環境執行;
持久性(Durable):事務完成以後,它對於數據的修改是永久性的,即便出現系統故障也可以保持。
併發事務處理帶來的問題
相對於串行處理來講,併發事務處理能大大增長數據庫資源的利用率,提升數據庫系統的事務吞吐量,從而能夠支持更多的用戶。但併發事務處理也會帶來一些問題,主要包括如下幾種狀況。
一、更新丟失(Lost Update):當兩個或多個事務選擇同一行,而後基於最初選定的值更新該行時,因爲每一個事務都不知道其餘事務的存在,就會發生丟失更新問題--最後的更新覆蓋了由其餘事務所作的更新。例如,兩個編輯人員製做了同一文檔的電子副本。每一個編輯人員獨立地更改其副本,而後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改副本的編輯人員覆蓋另外一個編輯人員所作的更改。若是在一個編輯人員完成並提交事務以前,另外一個編輯人員不能訪問同一文件,則可避免此問題。
二、髒讀(Dirty Reads):一個事務正在對一條記錄作修改,在這個事務完成並提交前,這條記錄的數據就處於不一致狀態;這時,另外一個事務也來讀取同一條記錄,若是不加控制,第二個事務讀取了這些「髒」數據,並據此作進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫作」髒讀」。
三、不可重複讀(Non-Repeatable Reads):一個事務在讀取某些數據後的某個時間,再次讀取之前讀過的數據,卻發現其讀出的數據已經發生了改變、或某些記錄已經被刪除了!這種現象就叫作「不可重複讀」。
四、幻讀(Phantom Reads):一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據,這種現象就稱爲「幻讀」。
事務隔離級別
在上面講到的併發事務處理帶來的問題中,「更新丟失」一般是應該徹底避免的。但防止更新丟失,並不能單靠數據庫事務控制器來解決,須要應用程序對要更新的數據加必要的鎖來解決,所以,防止更新丟失應該是應用的責任。
「髒讀」、「不可重複讀」和「幻讀」,其實都是數據庫讀一致性問題,必須由數據庫提供必定的事務隔離機制來解決。數據庫實現事務隔離的方式,基本上可分爲如下兩種。
一、一種是在讀取數據前,對其加鎖,阻止其餘事務對數據進行修改。
二、另外一種是不用加任何鎖,經過必定機制生成一個數據請求時間點的一致性數據快照(Snapshot),並用這個快照來提供必定級別(語句級或事務級)的一致性讀取。從用戶的角度來看,好像是數據庫能夠提供同一數據的多個版本,所以,這種技術叫作數據多版本併發控制(MultiVersion Concurrency Control,簡稱MVCC或MCC),也常常稱爲多版本數據庫。
數據庫的事務隔離越嚴格,併發反作用越小,但付出的代價也就越大,由於事務隔離實質上就是使事務在必定程度上 「串行化」進行,這顯然與「併發」是矛盾的。同時,不一樣的應用對讀一致性和事務隔離程度的要求也是不一樣的,好比許多應用對「不可重複讀」和「幻讀」並不敏感,可能更關心數據併發訪問的能力。
爲了解決「隔離」與「併發」的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別,每一個級別的隔離程度不一樣,容許出現的反作用也不一樣,應用能夠根據本身的業務邏輯要求,經過選擇不一樣的隔離級別來平衡 「隔離」與「併發」的矛盾。表20-5很好地歸納了這4個隔離級別的特性。
讀數據一致性及容許的併發反作用
隔離級別 讀數據一致性 髒讀 不可重複讀 幻讀
未提交讀(Read uncommitted) 最低級別,只能保證不讀取物理上損壞的數據 是 是 是
已提交度(Read committed) 語句級 否 是 是
可重複讀(Repeatable read) 事務級 否 否 是
可序列化(Serializable) 最高級別,事務級 否 否 否
最後要說明的是:各具體數據庫並不必定徹底實現了上述4個隔離級別,例如,Oracle只提供Read committed和Serializable兩個標準隔離級別,另外還提供本身定義的Read only隔離級別;SQL Server除支持上述ISO/ANSI SQL92定義的4個隔離級別外,還支持一個叫作「快照」的隔離級別,但嚴格來講它是一個用MVCC實現的Serializable隔離級別。MySQL支持所有4個隔離級別,但在具體實現時,有一些特色,好比在一些隔離級別下是採用MVCC一致性讀,但某些狀況下又不是
InnoDB有兩種模式的行鎖:
1)共享鎖(S):容許一個事務去讀一行,阻止其餘事務得到相同數據集的排他鎖。
( Select * from table_name where ……lock in share mode)
2)排他鎖(X):容許得到排他鎖的事務更新數據,阻止其餘事務取得相同數據集的共享讀鎖和排他寫鎖。(select * from table_name where…..for update)
爲了容許行鎖和表鎖共存,實現多粒度鎖機制;同時還有兩種內部使用的意向鎖(都是表鎖),分別爲意向共享鎖和意向排他鎖。
1)意向共享鎖(IS):事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。
2)意向排他鎖(IX):事務打算給數據行加行排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。
InnoDB行鎖模式兼容性列表
請求鎖模式
是否兼容
當前鎖模式 X IX S IS
X 衝突 衝突 衝突 衝突
IX 衝突 兼容 衝突 兼容
S 衝突 衝突 兼容 兼容
IS 衝突 兼容 兼容 兼容
若是一個事務請求的鎖模式與當前的鎖兼容,InnoDB就將請求的鎖授予該事務;反之,若是二者不兼容,該事務就要等待鎖釋放。
意向鎖是InnoDB自動加的,不需用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖;事務能夠經過如下語句顯示給記錄集加共享鎖或排他鎖。
一、共享鎖(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
二、排他鎖(X):SELECT * FROM table_name WHERE … FOR UPDATE。
InnoDB行鎖是經過給索引上的索引項加鎖來實現的,這一點MySQL與oracle不一樣,後者是經過在數據塊中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特色意味着:只有經過索引條件檢索數據,InnoDB才使用行級鎖,不然,InnoDB將使用表鎖!
在實際應用中,要特別注意InnoDB行鎖的這一特性,否則的話,可能致使大量的鎖衝突,從而影響併發性能。
查詢表級鎖爭用狀況
表鎖定爭奪:
能夠經過檢查table_locks_waited和table_locks_immediate狀態變量來分析系統上的表鎖定爭奪:
mysql> show status like ‘table%’;
+———————–+——-+
| Variable_name | Value |
+———————–+——-+
| Table_locks_immediate | 2979 |
| Table_locks_waited | 0 |
+———————–+——-+
2 rowsin set(0.00 sec))
若是Table_locks_waited的值比較高,則說明存在着較嚴重的表級鎖爭用狀況。
InnoDB行鎖爭奪:
能夠經過檢查InnoDB_row_lock狀態變量來分析系統上的行鎖的爭奪狀況:
mysql> show status like ‘innodb_row_lock%’;
+——————————-+——-+
| Variable_name | Value |
+——————————-+——-+
| InnoDB_row_lock_current_waits | 0 |
| InnoDB_row_lock_time | 0 |
| InnoDB_row_lock_time_avg | 0 |
| InnoDB_row_lock_time_max | 0 |
| InnoDB_row_lock_waits | 0 |
+——————————-+——-+
5 rowsin set(0.01 sec)
MyISAM寫鎖實驗:
對MyISAM表的讀操做,不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;對MyISAM表的寫操做,則會阻塞其餘用戶對同一表的讀和寫操做;MyISAM表的讀操做與寫操做之間,以及寫操做之間是串行的!根據如表20-2所示的例子能夠知道,當一個線程得到對一個表的寫鎖後,只有持有鎖的線程能夠對錶進行更新操做。其餘線程的讀、寫操做都會等待,直到鎖被釋放爲止。
USER1:
mysql> lock tablefilm_text write;
當前session對鎖定表的查詢、更新、插入操做均可以執行:
mysql> selectfilm_id,title fromfilm_text wherefilm_id = 1001;
USER2:
mysql> selectfilm_id,title fromfilm_text wherefilm_id = 1001;
等待
USER1:
釋放鎖:
mysql> unlock tables;
USER2:
得到鎖,查詢返回:
InnoDB存儲引擎的共享鎖實驗
USER1:
mysql> setautocommit = 0;
USER2:
mysql> setautocommit = 0;
USER1:
當前session對actor_id=178的記錄加share mode 的共享鎖:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178lock in share mode;
USER2:
其餘session仍然能夠查詢記錄,並也能夠對該記錄加share mode的共享鎖:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178lock in share mode;
USER1:
當前session對鎖定的記錄進行更新操做,等待鎖:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
等待
USER2:
其餘session也對該記錄進行更新操做,則會致使死鎖退出:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
USER1:
得到鎖後,能夠成功更新:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
Query OK, 1 row affected (17.67 sec)
Rowsmatched: 1 Changed: 1 Warnings: 0
InnoDB存儲引擎的排他鎖例子
USER1:
mysql> setautocommit = 0;
USER2:
mysql> setautocommit = 0;
USER1:
當前session對actor_id=178的記錄加for update的排它鎖:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178 forupdate;
USER2:
其餘session能夠查詢該記錄,可是不能對該記錄加共享鎖,會等待得到鎖:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178;
USER1:
當前session能夠對鎖定的記錄進行更新操做,更新後釋放鎖:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
USER2:
其餘session得到鎖,獲得其餘session提交的記錄:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178 forupdate;
更新性能優化的幾個重要參數
bulk_insert_buffer_size
批量插入緩存大小,這個參數是針對MyISAM存儲引擎來講的.適用於在一次性插入100-1000+條記錄時,提升效率.默認值是8M.能夠針對數據量的大小,翻倍增長.
concurrent_insert
併發插入,當表沒有空洞(刪除過記錄),在某進程獲取讀鎖的狀況下,其餘進程能夠在表尾部進行插入.
值能夠設0不容許併發插入, 1當表沒有空洞時,執行併發插入, 2無論是否有空洞都執行併發插入.
默認是1針對表的刪除頻率來設置.
delay_key_write
針對MyISAM存儲引擎,延遲更新索引.意思是說,update記錄時,先將數據up到磁盤,但不up索引,將索引存在內存裏,當表關閉時,將內存索引,寫到磁盤.值爲 0不開啓, 1開啓.默認開啓.
delayed_insert_limit, delayed_insert_timeout, delayed_queue_size
延遲插入,將數據先交給內存隊列,而後慢慢地插入.可是這些配置,不是全部的存儲引擎都支持,目前來看,經常使用的InnoDB不支持, MyISAM支持.根據實際狀況調大,通常默認夠用了。