你真的了解MySQL的鎖么
前言
鎖是計算機協調多個進程或者線程并發訪問某一資源的機制。鎖保證數據并發訪問的一致性、有效性;
鎖沖突也是影響數據庫并發訪問性能的一一個重要因素。鎖是在MySQL在服務器層和存儲層的并發控制。
MySQL的鎖機制
共享鎖和排它鎖
- 共享鎖(讀鎖 Table Read Lock)其他的事務可以讀,但是不能寫
- 怕它鎖(寫鎖 Table Write Lock)其他的事務不能讀取,也不能寫
鎖的粒度
MySQL不同的存儲引擎支持不同的鎖機制,所有的存儲引擎都實現了自己的鎖機制。服務器存儲層,完全不用了解存儲引擎中的實現。
- MyISAM和MEMORY存儲引擎采用的是表級鎖(table-level locking)
- InnoDB 存儲引擎支持行鎖,(row-level locking),同事支持表鎖
- 頁鎖(page-level locking )在一些特殊的引擎也支持,用的場景較少(BDB引擎支持頁鎖)。
默認的情況下行鎖跟表鎖都是自動獲取的,并不需要顯示的命令執行。除非在有些情況下,我們需要明確的進行鎖表,或者進行事務控制,以確保整個事務的完整性,這樣就需要使用事務控制語句和鎖定語句來進行控制。
幾種鎖對比
- 表鎖:開銷小,加鎖快,不會出現死鎖;鎖定的粒度大,發生鎖沖突的概率高,并發度低
- 行鎖:開銷大,加鎖慢,會出現死鎖,鎖定的粒度小,發生沖突的概率低,并發度高
- 頁鎖:開銷和加鎖介于表鎖和行鎖之間;會出現死鎖;鎖定的粒度介于表鎖跟行數之間,并發度一般。
一般來說,表鎖適合查詢為主,少量的更新。 行鎖適合具有大量按照索引條件并發更新少量的不同的數據,同時又有并發查詢的應用。
MyISAM表鎖
MyISAM存儲引起只支持表鎖,是MySQL開始幾個版本中唯一支持的鎖類型。隨著應用對事務的完整性和并發性的要求不斷提高,MySQL才支持基于事務的存儲引擎,事實上InnoDB是一個單獨的公司,后被Oracle收購。
查看鎖的爭用情況
table_locks_waited 和 table_locks_immediate
如果table_locks_waited的值,比較高,則說明存在著比較高的表級鎖的爭用情況。
MyISAM在執行查詢語句(select),都會自動的給涉及到表加讀鎖,只執行更新操作(update,delete,insert)前,會自動的給涉及到表加寫鎖,這個過程并不需要用戶干預,因此,用戶一般不需要直接用lock table
命令來給MyISAM表來顯示加鎖。在自動加鎖的s情況下,MyISAM總是一次獲得SQL語句所需要的全部鎖,這也正是MyISAM表不會出現死鎖的原因。MyISAM存儲引擎支持并發插入,以減少表之間的讀和寫操作之間的爭用。
InnoDB行級鎖和表級鎖
InnoDB鎖模式
- 共享鎖(S): 允許一個事務讀取一行,阻止其他事務獲得相同數據的排他鎖
- 排他鎖(X):允許獲得排他鎖的事務更新數據,阻止其他的事務獲得相同數據集的共享鎖和排他寫鎖。
- 意向共享鎖(IS):事務打算給數據行加共享鎖,事務給一個數據行加共享鎖前必須獲得表的IS鎖。(這個操作是DBA 無法干預的)
- 意向排他鎖(IX): 事務打算給數據行加怕他鎖,必須先獲得該表的一個意向排他鎖,(IX鎖)。(這個操作是DBA 無法干預的)
InnoDB 加鎖的方法
-
意向鎖是InnoDB自動加的,不需要用戶干預
-
對于update delete insert 語句,InnoDB會自動給涉及的數據集加排他鎖(X);
-
對于普通的select 語句,InnoDB不會加任何鎖
事務可以通過一下語句顯示的給記錄集加共享鎖或者排他鎖:- 共享鎖(S): select * from table_name where … lock in share mode,其他的session仍然可以查詢記錄,并且也可以對該記錄加 share mode的共享鎖,但是如果當前事務需要對該記錄進行更新操作,則很有可能造成死鎖。
- 排他鎖(X): select * from table_name Where … FOR UPDATE.其他session可以查詢該記錄,但是不能對該記錄加共享鎖或拍他鎖,而是等待獲得鎖。
-
隱式鎖定:
-
InnoDB 在事務的執行過程中,使用兩階段提交協議:隨時都可以執行鎖定,InnoDB會根據隔離級別在需要的時候自動加鎖。鎖只有在執行commit或者rollback的時候才會釋放,并且所有的鎖都是在同一時刻被釋放。
-
顯示鎖定
select * from table_name where … lock in share mode – 共享鎖
– 這個的意思就是 我對行數據加了share鎖,其他人能夠查看,但是不能夠進行DML(insert update delete)操作。 使用的場景:自己查詢到的數據不允許別人來修改,并且自己也不一定能夠修改(有可能其他的session 也使用了 in share mode 的方式加了S鎖) –select * from table_name Where ... FOR UPDATE --排他鎖 -- 這個鎖的意思其實相當于update語句,如果事務沒有及時的commit或者rollback會造成其他的事務長時間的等待。從而影響到數據的并發使用效率。
InnoDB行鎖的實現方式
- InnoDB行鎖是通過給索引上的索引項加鎖來實現的,Oracle 是通過給數據塊中的對應數據行來加鎖進行實現。InnoDB這種行鎖設計實現意味著:只有通過索引條件檢索數據,才能使用行鎖,否則就會使用表鎖
- 無論是使用主鍵索引、唯一索引、普通索引,InnoDB都會使用行鎖來對數據進行加鎖。
- 由于MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,所以雖然多個session是訪問不同行的記錄,但是如果是使用相同行的索引鍵,是會出現鎖沖突的(后使用這些索引的session需要等待先使用索引的session釋放鎖后,才能獲取鎖)。
InnoDB間隙鎖
范圍檢索的時候,InnoDB會給符合條件的已有數據記錄的索引項加鎖。對于鍵值在條件范圍內并不存在的記錄, 叫做“間隙(GAP)“,InnoDB 也會對這個間隙加鎖,這個鎖叫做臨建鎖(Next-key lock).
顯然在范圍檢索的時候,這種加鎖機制會阻塞符合條件范圍鍵值的并發插入,會造成嚴重的等待。所以在實際的應用開發中,尤其是并發插入的時候,我們應該盡量的優化業務邏輯,使用等值條件來訪問更新數據,避免使用范圍條件。
間隙鎖的作用:
- 防止幻讀,滿足隔離級別的要求
- 滿足恢復和復制的需求。
MySQL通過BINGLOG錄入執行成功的insert update delete 等DML語句.并且實現了數據庫的主從復制和數據恢復。
- MySQL 的恢復實際上是執行了BINLOG中的SQL語句
- MySQL 的BINGLOG 是按照事務提交的先后順序記錄的,恢復也是按照這個順序執行的。也就是說:在一個事務未提交前,其他的并發事務不能滿足插入滿足其鎖定條件的任何記錄,也就是不允許出現幻讀。
Record Lock & Gap Lock & Next-key Lock
-
記錄鎖(Record Lock):
當我們對于唯一性的 索引(包括唯一索引和主鍵索引)使用等值查詢,
精準匹配到一條記錄的時候,這個時候使用的就 是記錄鎖,比如where id=1 -
間隙鎖(Gap Lock):
當我們查詢的記錄不存在,沒有命中任何一個 record,無論是用等值 查詢還是范圍查詢的時候,它使用的都是間隙鎖。
(注意:間隙鎖主要是阻塞插入 insert。相同的間隙鎖之間不沖突,Gap Lock只在RR事務隔離級別才能使用) -
臨鍵鎖(Next-key Lock):
當我們使用了范圍查詢,不僅僅命中了Record記錄,還包含了Gap間隙,在這種情況下我們使用的就是臨鍵鎖
(它是MySQL里面默認的行鎖算法,相當于記錄鎖加上間隙鎖。) -
鎖的退化:
1.唯一性索引等值查詢匹配到一條記錄的時候, 退化成記錄鎖。
2.沒有匹配到任何記錄的時候,退化成間隙鎖 -
總結: Record Lock 、Gap Lock 、Next-key Lock 都只是鎖的一種算法
不同隔離級別下一致性讀跟鎖的差異
鎖和多版本數據MVCC是InnoDB實現一致性讀和ISO/ANSI SQL92隔離級別的手段。因此在不同的隔離級別下,InnoDB處理SQL時候,采用的一致性讀策略和加鎖的策略是不一樣的。
-
RU(Read Uncommited):
- 不加鎖
-
RC (Read Commited):
- 普通的快照讀 select mvcc實現
- 加鎖的 select 都使用記錄鎖,因為基本不用Gap Lock
- 外鍵約束檢查以及重復鍵檢查時會使用間隙鎖Gap Lock
-
RR(Repeatable Read):
- 普通的select都是快照讀,使用MVCC實現
- 加鎖的select以及更新操作等語句使用當前讀底層使用記錄鎖、間隙鎖、臨鍵鎖
-
Serializable:
- 所有的select語句都會被隱式的轉化為in share mode,會和update、delete等操作互斥
-
RC跟RR的幻讀問題:
- RC:每次讀取,都會創建一個新的read view。這樣就能讀取到其他事務已經COMMIT的內容
- RR:ReadView是在first touch read時創建的,也就是執行事務中的第一條SELECT語句的瞬間,后續所有的SELECT都是復用這個
- read view,所以能保證每次讀取的一致性(可重復讀的語義)
- 總結:所以對于InnoDB來說,RR雖然比RC隔離級別高,但是開銷反而相對少
- 因此在我們的應用當中,應該盡量使用較低的隔離級別,減少鎖的爭用效率,實際上通過優化事務的邏輯,大部分應用使用 RC的隔離級別就足夠了。針對確實需要更高隔離級別的事務,可以通過執行程序中執行set session transaction isolation level repeatable read 或者 set session transaction isolation level serializable 動態改變隔離級別的方式滿足需求。
-
ReadView
- RU: 直接讀取最新版本最新記錄
- RR:每個事務開始的時候會將當前系統中的所有活躍事務拷貝到一個列表生成一個ReadView
- RC:每個語句開始的時候,會將當前系統中的所有活躍事務拷貝到一個列表生成一個ReadView
InnoDB行鎖競爭分析
- Innodb_row_lock_current_waits 當前等待的鎖的數量
- Innodb_row_lock_time 從系統啟動到現在鎖定的總時間長度,單位ms;
- Innodb_row_lock_time_avg每次等待所花平均時間
- Innodb_row_lock_time_max從系統啟動到現在等待最長的一次所花的時間;
- Innodb_row_lock_waits從系統啟動到現在總共等待的次數。
LOCK TABLES & UNLOCK TABLES
- lock tables 可以鎖定用于當前線程的表。如果表被其他的線程鎖定,則當前線程會等待,直到可以獲取所有的鎖定為止。
- unlock tables 可以釋放當前線程獲得任何鎖定,當前線程執行另一個lock tables,或者是與服務器連接被關閉的時候,所有當前線程鎖定的表被隱含的解鎖。
Lock tables orders read local, order_detail read local;
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;
– 假設我們要統計訂單表order 和訂單明細order_detail 數據是否相等,可以這樣進行操作。加入local的選項,作用是為了當持有表鎖的同時,其他的用戶可以并發的插入。
什么是死鎖,死鎖怎么解決
死鎖是指兩個或者多個事務在同一資源上相互占用,并請求鎖定對方的資源,從而導致惡性循環的現象。
InnDB 引擎采用的是 wait for graph 等待圖的方法自動檢測死鎖,如果發現死鎖會自動回滾一個事務。
- 不同程序并發存儲多個表,盡量約定以相同的順序訪問表,可以大大的降低死鎖的機會。
- 在同一個事務中,盡可能做到一次鎖定所需要的資源,減少死鎖的產生。
- 對于容易產生死鎖的業務部門,可以嘗試升級鎖定的顆粒度,通過表級鎖定來減少死鎖產生的概率。
其他方案:在業務不好處理的情況下,建議采用分布式鎖或者樂觀鎖來解決。
樂觀鎖&悲觀鎖
悲觀鎖: 假定會發生并發沖突,屏蔽一切可能違反數據完整性的操作。在查詢完數據的時候就把事務鎖起來,直到提交事務。實現方式使用數據庫中的鎖機制。
Java synchronized 就是屬于悲觀鎖的一種試下,每次線程要修改數據的時候都要先獲得鎖,保障同一時刻只能有一個線程能夠操作數據,其他的線程會被block。
select * from table for update -- 悲觀鎖 使用場景,某些支付-賬務場景會使用到悲觀鎖,來嚴格控制事務
樂觀鎖:假設不會發生并發沖突,只是在提交操作的時候檢查是否違反數據的完整性。update的時候將事務鎖起來,通過version的范式進行鎖定。實現的方式一般使用版本號或者CAS算法實現。
在Java 中 Atomic 包就是樂觀鎖的一種實現,AtomicInteger通過CAS(Compare and set) 實現操作線程安全的自增。
update t_stock set stock = stock -1 where stock >0 ;-- rc 級別下就能防止超賣,這種就是樂觀鎖的實現其實也就是CAS算法
update table set value = newValue ,version = versionValue +1 where version = versionValue; -- 采用版本號來控制
一些使用鎖的優化建議
- 盡量使用較低的隔離級別;
- 精心設計索引, 并盡量使用索引訪問數據, 使加鎖更精確, 從而減少鎖沖突的機會
- 選擇合理的事務大小,小事務發生鎖沖突的幾率也更小
- 給記錄集顯示加鎖時,最好一次性請求足夠級別的鎖。比如要修改數據的話,最好直接申請排他鎖,而不是先申請共享鎖,修改時再請求排他鎖,這樣容易產生死鎖
- 不同的程序訪問一組表時,應盡量約定以相同的順序訪問各表,對一個表而言,盡可能以固定的順序存取表中的行。這樣可以大大減少死鎖的機會
- 盡量用相等條件訪問數據,這樣可以避免間隙鎖對并發插入的影響
- 不要申請超過實際需要的鎖級別
- 除非必須,查詢時不要顯示加鎖。 MVCC可以實現事務中的查詢不用加鎖,優化事務性能;MVCC只在COMMITTED READ(讀提交)和REPEATABLE READ(可重復讀)兩種隔離級別下工作
- 對于一些特定的事務,可以使用表鎖來提高處理速度或減少死鎖的可能
智能推薦
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
猜你喜歡
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...
19.vue中封裝echarts組件
19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...