MyISAM
和MEMORY支持表鎖
Innodb
既支持行鎖,也支持表鎖,默認行鎖
檢查table_locks_waited
和table_locks_immediate
狀態變量分析html
table_locks_waited 的值越高,則說明存在嚴重的表級鎖的爭用狀況
MySQL的表鎖有兩種模式數據庫
鎖模式的兼容以下表segmentfault
是否兼容 | 請求none | 請求讀鎖 | 請求寫鎖 |
---|---|---|---|
當前處於讀鎖 | 是 | 是 | 否 |
當前處於寫鎖 | 是 | 否 | 否 |
可見,對MyISAM表的讀操做,不會阻塞其餘用戶對同一表的讀請求,但會阻塞對同一表的寫請求;
對MyISAM表的寫操做,則會阻塞其餘用戶對同一表的讀和寫請求;服務器
MyISAM表的讀和寫操做之間,以及寫和寫操做之間是串行的!(當某一線程得到對一個表的寫鎖後,只有持有鎖的線程能夠對錶進行更新操做.其餘線程的讀、寫操做都會等待,直到鎖被釋放爲止)session
對於 MyISAM 引擎數據結構
select
前,會自動給涉及的全部表加 讀 不須要用戶直接顯式用lock table
命令併發
對於給MyISAM顯式加鎖,通常是爲了在必定程度上模擬事務操做,實現對某一個時間點多個表一致性讀取性能
記錄各訂單的總金額total
優化
記錄各訂單每一產品的金額小計subtotal
spa
假設咱們須要檢查這兩個表的金額合計是否相符,可能就須要執行以下兩條SQL
圖片上傳失敗...(image-3017e3-1547370332969)
這時,若是不先給這兩個表加鎖,就可能產生錯誤的結果;
由於第一條語句執行過程當中,order_detail
表可能已經發生了改變.
所以,正確寫法應該以下
圖片上傳失敗...(image-8081d7-1547370332969)
也就是說,在執行LOCK TABLES後,只能訪問顯式加鎖的這些表,不能訪問未加鎖的表;
同時,若是加的是讀鎖,那麼只能執行查詢操做,而不能執行更新操做
其實,在自動加鎖的狀況下也基本如此,MySQL會一次得到SQL語句所須要的所有鎖.這也正是MyISAM表不會出現死鎖(Deadlock Free)的緣由
session1 | session2 |
---|---|
得到表 film_text 的讀鎖 lock table film_text read; |
|
可select * from film_text | 可select * from film_text |
不能查詢沒有鎖定的表 :select * from film | 可查詢/更新未鎖定的表: select * from film |
插入或更新鎖定表會提示錯誤 update...from film_text | 更新鎖定表會等待 update...from film_text |
釋放鎖 unlock tables | 等待 |
| 得到鎖,更新成功 |
當使用lock tables
時,不只須要一次鎖定用到的全部表
且同一表在SQL語句中出現多少次,就要經過與SQL語句中別名鎖多少次
lock table actor read
會提示錯誤
select a.first_name.....
須要對別名分別鎖定
lock table actor as a read,actor as b read;
在必定條件下,MyISAM
也支持併發插入和讀取
控制其併發插入的行爲,其值分別能夠爲
MyISAM容許在一個讀表的同時,另外一個進程從表尾插入記錄(MySQL的默認設置)
若無讀線程,新行插入空洞中
能夠利用MyISAM
的併發插入特性,來解決應用中對同表查詢和插入的鎖爭用
例如,將concurrent_insert
系統變量設爲2,老是容許併發插入;
同時,經過按期在系統空閒時段執行OPTIONMIZE TABLE語句來整理空間碎片,收到因刪除記錄而產生的中間空洞
刪除操做
不會重整整個表,只是把 行 標記爲刪除,在表中留下空洞
MyISAM傾向於在可能時填滿這些空洞,插入時就會重用這些空間,無空洞則把新行插到表尾
MyISAM
的讀和寫鎖互斥,讀操做串行的
MyISAM
表的讀鎖,同時另外一個進程也請求同表的寫鎖,MySQL如何處理呢?寫進程先得到鎖!!!
不只如此,即便讀進程先請求先到鎖等待隊列,寫請求後到,寫鎖也會插到讀請求以前!!!
這是由於MySQL認爲寫請求通常比讀請求重要
這也正是MyISAM
表不適合有大量更新 / 查詢
操做應用的緣由
大量的更新操做會形成查詢操做很難得到讀鎖,從而可能永遠阻塞
幸虧,咱們能夠經過一些設置來調節MyISAM
的調度行爲
low-priority-updates
使MyISAM引擎默認給予讀請求以優先權利
SET LOW_PRIORITY_UPDATES=1
使該鏈接發出的更新請求優先級下降
LOW_PRIORITY
屬性下降該語句的優先級
雖然上面3種方法都是要麼更新優先,要麼查詢優先,但仍是能夠用其來解決查詢相對重要的應用(如用戶登陸系統)中,讀鎖等待嚴重的問題
另外,MySQL也提供了一種折中的辦法來調節讀寫衝突;
即給系統參數max_write_lock_count
設置一個合適的值;
當一個表的讀鎖達到這個值後,MySQL便暫時將寫請求的優先級下降,給讀進程必定得到鎖的機會
* * *
InnoDB與MyISAM的最大不一樣有兩點
行級鎖和表級鎖原本就有許多不一樣之處,另外,事務的引入也帶來了一些新問題
一組SQL語句組成的邏輯處理單元
事務是一個原子操做單元,其對數據的修改,要麼全都執行,要麼全都不執行
在事務開始和完成時,數據都必須保持一致狀態
這意味着全部相關的數據規則都必須應用於事務的修改,以保持完整性
事務結束時,全部的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的
一個事務所作的修改在最終提交前對其餘事務不可見
一旦事務提交,它對於數據的修改會持久化到DB
相對於串行處理來講,併發事務處理能大大增長數據庫資源的利用率,提升數據庫系統的事務吞吐量,從而能夠支持能夠支持更多的用戶
但併發事務處理也會帶來一些問題,主要包括如下幾種狀況
當多個事務選擇同一行,而後基於最初選定值更新該行時,因爲事務隔離性,最後的更新覆蓋了其餘事務所作的更新.
例如,兩個編輯人員製做了同一文檔的電子副本。每一個編輯人員獨立地更改其副本,而後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改保存其更改副本的編輯人員覆蓋另外一個編輯人員所作的修改;
若是在一個編輯人員完成並提交事務以前,另外一個編輯人員不能訪問同一文件,則可避免此問題
一個事務正在對一條記錄作修改,在該事務提交前,這條記錄的數據就處於不一致狀態
這時,另外一個事務也來讀取同一條記錄,讀取了這些未提交的數據
一個事務在讀取某些數據已經發生了改變、或某些記錄已經被刪除
一個事務按相同的查詢條件從新讀取之前檢索過的數據,卻發現其餘事務插入了知足其查詢條件的新數據
在併發事務的問題中,「更新丟失」一般應該是徹底避免的;
但防止更新丟失,並不能單靠數據庫事務控制器來解決,須要應用程序對要更新的數據加必要的鎖來解決,所以,防止更新丟失應該是應用的責任
「髒讀」、「不可重複讀」和「幻讀」,其實都是數據庫讀一致性
問題,必須由數據庫提供必定的事務隔離機制來解決
數據庫實現事務隔離的方式,基本能夠分爲如下兩種
加鎖
,防止其餘事務對數據進行修改不加任何鎖
,經過必定機制生成一個數據請求時間點的一致性數據快照
,並用這個快照來提供必定級別(語句級或事務級)的一致性讀取.從用戶的角度,好像是數據庫能夠提供同一數據的多個版本,所以,這種技術叫作數據多版本併發控制(MultiVersion Concurrency Control,MVCC),也常常稱爲多版本數據庫
數據庫的事務隔離級別越嚴格,併發反作用越小,但付出的代價也越大
由於事務隔離實質上就是使事務在必定程度上「串行化」進行,這顯然與「併發」矛盾
爲了解決「隔離」與「併發」的矛盾,ANSI SQL定義了4種隔離級別
隔離級別/讀數據一致性及容許的併發反作用 | 讀數據一致性 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
未提交讀(Read uncommitted) | 最低級別,只能保證不讀取物理上損壞的數據 | 是 | 是 | 是 |
已提交度(Read committed) | 語句級 | 否 | 是 | 是 |
可重複讀(Repeatable read) | 事務級 | 否 | 否 | 是 |
可序列化(Serializable) | 最高級別,事務級 | 否 | 否 | 否 |
若是發現爭用比較嚴重,如Innodb_row_lock_waits
和Innodb_row_lock_time_avg
的值比較高
進一步觀察發生鎖衝突的表,數據行等,並分析鎖爭用的緣由
默認狀況每15秒會向日志中記錄監控的內容;
若是長時間打開會致使.err文件變得很是巨大;
因此確認緣由後,要刪除監控表關閉監視器,或者經過使用--console選項來啓動服務器以關閉寫日誌功能
InnoDB支持如下兩種類型的行鎖
若事務 T 對數據對象 A 加了 S 鎖;
則事務 T 能夠讀 A 但不能修改 A;
其它事務只能再對它加 S 鎖,而不能加 X 鎖,直到 T 釋放 A 上的 S 鎖;
這保證了,其餘事務能夠讀 A,但在事務 T 釋放 S 鎖以前,不能對 A 作任何修改操做.
若事務 T 對數據對象A加 X 鎖;
事務 T 能夠讀 A 也能夠修改 A;
其餘事務不能對 A 加任何鎖,直到 T 釋放 A 上的鎖;
這保證了,其餘事務在 T 釋放 A 上的鎖以前不能再讀取和修改 A .
MySQL InnoDB默認行級鎖
行級鎖都是基於索引的,若一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住
爲了容許行/表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks)
這兩種意向鎖都是表鎖
事務打算給數據行共享鎖;
事務在給一個數據行加共享鎖前必須先取得該表的IS鎖
事務打算給數據行加排他鎖;
事務在給一個數據行加排他鎖前必須先取得該表的IX鎖
當前鎖/是否兼容/請求鎖 | X | IX | S | IS |
---|---|---|---|---|
X | 衝突 | 衝突 | 衝突 | 衝突 |
IX | 衝突 | 兼容 | 衝突 | 兼容 |
S | 衝突 | 衝突 | 兼容 | 兼容 |
IS | 衝突 | 兼容 | 兼容 | 兼容 |
若是一個事務請求的鎖模式與當前鎖兼容,InnoDB就請求的鎖授予該事務;
反之,若是二者二者不兼容,該事務就要等待鎖釋放
意向鎖是InnoDB自動加的,不需用戶干預.
對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及及數據集加排他鎖(X);
對於普通SELECT語句,InnoDB不會加任何鎖.
對於SELECT語句,能夠經過如下語句顯式地給記錄加讀/寫鎖
共享鎖語句主要用在須要數據依存關係時確認某行記錄是否存在;
並確保沒有人對這個記錄UPDATE或DELETE.
但若是當前事務也須要對該記錄進行更新,則頗有可能形成死鎖;
對於鎖定行記錄後須要進行更新操做的應用,應該使用排他鎖語句.
session_1 | session_2 |
---|---|
set autocommit=0,select * from actor where id =1 | set autocommit=0,select * from actor where id =1 |
當前seesion對id爲1的記錄加入共享鎖 select * from actor where id =1 lock in share mode | |
| 其餘seesion仍然能夠查詢,並對該記錄加入 select * from actor where id =1 lock in share mode |
當前session對鎖定的記錄進行更新,等待鎖 update。。。where id=1 | |
| 當前session對鎖定記錄進行更新,則會致使死鎖退出 update。。。where id=1 |
| 得到鎖,更新成功 |
session_1 | session_2 |
---|---|
set autocommit=0,select * from actor where id =1 | set autocommit=0,select * from actor where id =1 |
當前seesion對id爲1的記錄加入for update 共享鎖 select * from actor where id =1 for update | |
| 可查詢該記錄select from actor where id =1,可是不能再記錄共享鎖,會等待得到鎖select from actor where id =1 for update |
更新後釋放鎖 update。。。 commit | |
| 其餘session,得到鎖,獲得其餘seesion提交的記錄 |
行鎖是經過給索引上的索引項加鎖來實現
若是沒有索引,InnoDB將經過隱藏的聚簇索引來對記錄加鎖
行鎖實現特色意味着:
若是不經過索引條件檢索數據,那麼Innodb將對錶的全部記錄加鎖,和表鎖同樣
當咱們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據的索引項加鎖;
對於鍵值在條件範圍內但並不存在的記錄,叫作「間隙(GAP)」,InnoDB也會對這個「間隙」加鎖,這種鎖機制就是所謂的間隙鎖(Next-Key鎖).
舉例來講,假如emp表中只有101條記錄,其empid的值分別是1,2,...,100,101,下面的SQL:
InnoDB 不只會對符合條件的 empid 值爲 101 的記錄加鎖;
也會對 empid
大於101
(這些記錄並不存在)的「間隙」加鎖
對於上例,若不使用間隙鎖,若是其餘事務插入 empid 大於 100 的任何記錄,;
那麼本事務若是再次執行上述語句,就會發生幻讀
在使用範圍條件檢索並鎖定記錄時;
InnoDB 這種加鎖機制會阻塞符合條件範圍內鍵值的併發插入,這每每會形成嚴重的鎖等待;
所以,在實際開發中,尤爲是併發插入較多的應用;
咱們要儘可能優化業務邏輯,儘可能使用相等條件來訪問更新數據
,避免使用範圍條件.
對於InnoDB,在絕大部分狀況下都應該使用行鎖
由於事務
,行鎖
每每是咱們選擇InnoDB的理由
但在個別特殊事務中,也能夠考慮使用表鎖
若使用默認的行鎖,不只該事務執行效率低(由於須要對較多行加鎖,加鎖是須要耗時的);
並且可能形成其餘事務長時間鎖等待和鎖衝突;
這種狀況下能夠考慮使用表鎖來提升該事務的執行速度
這種狀況也能夠考慮一次性鎖定事務涉及的表,從而避免死鎖、減小數據庫因事務回滾帶來的開銷
固然,應用中這兩種事務不能太多,不然,就應該考慮使用MyISAM
在InnoDB下 ,使用表鎖要注意
LOCK TALBES
雖然能夠給InnoDB
加表鎖表鎖不是由InnoDB
引擎層管理的,而是由其上一層MySQL Server負責;
僅當autocommit=0、innodb_table_lock=1(默認設置)
,InnoDB 引擎層才知道MySQL加的表鎖,MySQL Server才能感知InnoDB加的行鎖;
這種狀況下,InnoDB才能自動識別涉及表鎖的死鎖
不然,InnoDB將沒法自動檢測並處理這種死鎖
LOCK TALBES
對InnoDB
鎖時要注意,要將autocommit
設爲0,不然MySQL不會給表加鎖事務結束前,不要用UNLOCK TALBES
釋放表鎖,由於它會隱式地提交事務
COMMIT或ROLLBACK不能釋放用LOCK TALBES
加的表鎖,必須用UNLOCK TABLES釋放表鎖,正確的方式見以下語句
MyISAM表鎖是deadlock free的,這是由於MyISAM老是一次性得到所需的所有鎖,要麼所有知足,要麼等待,所以不會出現死鎖
但在InnoDB中,除單個SQL組成的事務外,鎖是逐步得到的,這就決定了InnoDB發生死鎖是可能的
發生死鎖後,InnoDB通常都能自動檢測到,並使一個事務釋放鎖並退回,另外一個事務得到鎖,繼續完成事務
這須要經過設置鎖等待超時參數innodb_lock_wait_timeout來解決
須要說明的是,這個參數並非只用來解決死鎖問題,在併發訪問比較高的狀況下,若是大量事務因沒法當即獲取所需的鎖而掛起,會佔用大量計算機資源,形成嚴重性能問題,甚至拖垮數據庫
咱們經過設置合適的鎖等待超時閾值,能夠避免這種狀況發生。
一般來講,死鎖都是應用設計的問題,經過調整業務流程、數據庫對象設計、事務大小、以及訪問數據庫的SQL語句,絕大部分均可以免
下面就經過實例來介紹幾種死鎖的經常使用方法。
儘可能約定以相同的順序
訪問表
事先對數據排序
,保證每一個線程按固定的順序來處理記錄
應直接申請排他鎖,而不該該先申請共享鎖
可重複讀
下,若是兩個線程同時對相同條件記錄用SELECT...ROR UPDATE
加排他寫鎖在沒有符合該記錄狀況下,兩個線程都會加鎖成功
程序發現記錄尚不存在,就試圖插入一條新記錄,若是兩個線程都這麼作,就會出現死鎖
這種狀況下,將隔離級別改爲READ COMMITTED,就能夠避免問題
SELECT...FOR UPDATE
判斷是否存在符合條件的記錄,沒有 -> 插入記錄;
此時,只有一個線程能插入成功,另外一個線程會出現鎖等待.
當第1個線程提交後,第2個線程會因主鍵重出錯,但雖然這個線程出錯了,卻會得到一個排他鎖!這時若是有第3個線程又來申請排他鎖,也會出現死鎖.
對於這種狀況,能夠直接作插入操做,而後再捕獲主鍵重異常,或者在遇到主鍵重錯誤時,老是執行ROLLBACK釋放得到的排他鎖
若是出現死鎖,能夠用SHOW INNODB STATUS命令來肯定最後一個死鎖產生的緣由和改進措施。
共享讀鎖和排他寫鎖
之間,以及排他寫鎖之間
互斥,即讀寫串行MyISAM
容許查詢/插入併發,可利用這一點來解決應用中對同一表查詢/插入的鎖爭用問題MyISAM
默認的鎖調度機制是寫優先,這並不必定適合全部應用,用戶能夠經過設置LOW_PRIPORITY_UPDATES
參數或在INSERT、UPDATE、DELETE語句中指定LOW_PRIORITY
選項來調節讀寫鎖的爭用MyISAM
表可能會出現嚴重的鎖等待,能夠考慮採用InnoDB表來減小鎖衝突若是不經過索引訪問數據,InnoDB會使用表鎖
在瞭解InnoDB的鎖特性後,用戶能夠經過設計和SQL調整等措施減小鎖衝突和死鎖
本文由博客一文多發平臺 OpenWrite 發佈!