(DBA之路【五】)關於鎖的故事

首先很抱歉:這篇文章我其實整合了很多別人的文章,但是因爲太多,一開始被沒留意出處所以很難聲明來源,很抱歉,但是這篇文章只用來作爲學習筆記,作爲新手,我以後會注意的。

(一):什麼是鎖:

我覺得鎖這個東西就像未退出的word文檔,begin開始事務後到commit之前,如果做錯了可以rollbak回滾。

有了鎖之後,不像antocommit那樣每句提交浪費效率,而且innodb還有行鎖,這樣也不佔內存,修改效率高。


鎖的分類:

讀鎖:

也叫共享鎖、S鎖,若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對AS鎖,而不能加X鎖,直到T釋放A上的鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。

寫鎖:

又稱排他鎖、X鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。這保證了其他事務在T釋放A上的鎖之前不能再讀取和修改A

表鎖:操作對象是數據表。Mysql大多數鎖策略都支持(常見mysql innodb),是系統開銷最低但併發性最低的一個鎖策略。事務t對整個表加讀鎖,則其他事務可讀不可寫,若加寫鎖,則其他事務增刪改都不行。

行級鎖:操作對象是數據表中的一行。是MVCC技術用的比較多的,但在MYISAM用不了,行級鎖用mysql的儲存引擎實現而不是mysql服務器。但行級鎖對系統開銷較大,處理高併發較好。


(二)MySQL鎖概述

相對其他數據庫而言,MySQL的鎖機制比較簡單,其最顯著的特點是不同的存儲引擎支持不同的鎖機制。比如,MyISAM和MEMORY存儲引擎採用的是表級鎖(table-level locking);BDB存儲引擎採用的是頁面鎖(page-level locking),但也支持表級鎖;InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認情況下是採用行級鎖。


開銷、加鎖速度、死鎖、粒度、併發性能

         表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。

         行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。

         頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。


MySQL表級鎖的鎖模式

MySQL的表級鎖有兩種模式:表共享讀鎖(Table Read Lock)和表獨佔寫鎖(Table Write Lock)

如何加鎖:

例子:lock table actor read;(但是通過別名訪問會提示錯誤

需要對別名分別鎖定:lock table actor as a read,actor as b read;此時按照別名的查詢可以正確執行

釋放鎖

mysql> unlock tables;


(三)大神說的需要的一些基礎背景知識:

1)堆組織表:(無序表)此類型的表中,數據會以堆的方式進行管理,增加數據時候,會使用段中找到的第一個能放下
此數據的自由空間。當從表中刪除數據時候,則允許以後的UPDATE和INSERT重用這部分空間,它是以一種有些隨機的方式使用。

e.g:

  create table t(   
   a int,  
   b varchar2(4000) default rpad('*', 4000, '*'),  
   c varchar2(3000) default rpad('*', 3000,'*')  
 );  
   
 insert into t(a) values (1);  
 insert into t(a) values (2);  
 insert into t(a) values (3);  
 delete from t where a=2;  
 insert into t(a) values (4);  
   
 SQL> select a from t;  
   
          A  
 ----------  
          1  
          4  
          3 

2)索引組織表(index organized table, IOT):就是存儲在一個索引結構中的表,數據按主鍵進行存儲和排序。
   適用的場景:
    a.完全由主鍵組成的表。這樣的表如果採用堆組織表,則表本身完全是多餘的開銷,
      因爲所有的數據全部同樣也保存在索引裏,此時,堆表是沒用的。
    b.代碼查找表。如果你只會通過一個主鍵來訪問一個表,這個表就非常適合實現爲IOT.
 c.如果你想保證數據存儲在某個位置上,或者希望數據以某種特定的順序物理存儲,IOT就是一種合適的結構。 IOT提供如下的好處:
  ·提高緩衝區緩存效率,因爲給定查詢在緩存中需要的塊更少。
  ·減少緩衝區緩存訪問,這會改善可擴縮性。
  ·獲取數據的工作總量更少,因爲獲取數據更快。
  ·每個查詢完成的物理I/O更少。
   如果經常在一個主鍵或唯一鍵上使用between查詢,也是如此。如果數據有序地物理存儲,就能提升這些查詢的性能。
    語法:create table indexTable(
  ID varchar2 (10), 
  NAME varchar2 (20), 
  constraint pk_id primary key (ID)
  ) organization index;


3)MVCC:mvcc爲每個記錄行追加了2個列,一個保存了記錄的創建時間,也就是insert的插入時間,這個其實是系統的版本號,一個是保存了記錄的刪除時間,也就是delete的時間,mvcc自身也有當前事務版本號,每次執行操作版本號都會自動自動加一。mvcc要求在select數據的時候,只查找記錄的創建系統版本號小於或者等於當前的事務版本號而且行的刪除版本號要麼未定義要麼,要麼高於當前的事務版本號即可。


4)二項鎖2PL:事務提交後所有鎖將會被釋放

分爲兩個階段:1)無鎖釋放,全部加鎖,且鎖的數目只增不減

              2)全部釋放,一鎖都不用

可以簡單的理解在事務提交前,加的鎖只能增加不能釋放,只會越來越多,而事務一旦提交,所有鎖將會被釋放。


5)查詢mysql的sql執行計劃(在sql語句前加上explain就行了):即在查詢前就能預先估計查詢究竟要涉及多少行、使用哪些索引、運行多久這些類似信息。

例子:

EXPLAIN

SELECT COUNT(*) FROM ××××××WHERE XXXXX


EXPLAIN列的解釋

table 
顯示這一行的數據是關於哪張表的

type 
這是重要的列,顯示連接使用了何種類型。從最好到最差的連接類型爲const、eq_reg、ref、range、indexhe和ALL(後面有詳細說明)

possible_keys 
顯示可能應用在這張表中的索引。如果爲空,沒有可能的索引。可以爲相關的域從WHERE語句中選擇一個合適的語句

key 
實際使用的索引。如果爲NULL,則沒有使用索引。很少的情況下,MYSQL會選擇優化不足的索引。這種情況下,可以在SELECT語句中使用USE INDEX(indexname)來強制使用一個索引或者用IGNORE INDEX(indexname)來強制MYSQL忽略索引

key_len 
使用的索引的長度。在不損失精確性的情況下,長度越短越好

ref 
顯示索引的哪一列被使用了,如果可能的話,是一個常數

rows 
MYSQL認爲必須檢查的用來返回請求數據的行數

Extra 
壞的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,結果是檢索會很慢


extra列返回的描述的意義

  Distinct 
一旦MYSQL找到了與行相聯合匹配的行,就不再搜索了

  Not exists 
MYSQL優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標準的行,就不再搜索了

  Range checked for each

  Record(index map:#) 
沒有找到理想的索引,因此對於從前面表中來的每一個行組合,MYSQL檢查使用哪個索引,並用它來從表中返回行。這是使用索引的最慢的連接之一

  Using filesort 
看到這個的時候,查詢就需要優化了。MYSQL需要進行額外的步驟來發現如何對返回的行排序。它根據連接類型以及存儲排序鍵值和匹配條件的全部行的行指針來排序全部行

  Using index 
列數據是從僅僅使用了索引中的信息而沒有讀取實際的行動的表返回的,這發生在對錶的全部的請求列都是同一個索引的部分的時候

  Using temporary 
看到這個的時候,查詢需要優化了。這裏,MYSQL需要創建一個臨時表來存儲結果,這通常發生在對不同的列集進行ORDER BY上,而不是GROUP BY上

  Where used 
使用了WHERE從句來限制哪些行將與下一張表匹配或者是返回給用戶。如果不想返回表中的全部行,並且連接類型ALL或index,這就會發生,或者是查詢有問題

不同連接類型的解釋(按照效率高低的順序排序)

  system 
表只有一行:system表。這是const連接類型的特殊情況

  const 
表中的一個記錄的最大值能夠匹配這個查詢(索引可以是主鍵或惟一索引)。因爲只有一行,這個值實際就是常數,因爲MYSQL先讀這個值然後把它當做常數來對待

  eq_ref 
在連接中,MYSQL在查詢時,從前面的表中,對每一個記錄的聯合都從表中讀取一個記錄,它在查詢使用了索引爲主鍵或惟一鍵的全部時使用

  ref 
這個連接類型只有在查詢使用了不是惟一或主鍵的鍵或者是這些類型的部分(比如,利用最左邊前綴)時發生。對於之前的表的每一個行聯合,全部記錄都將從表中讀出。這個類型嚴重依賴於根據索引匹配的記錄多少—越少越好

  range 
這個連接類型使用索引返回一個範圍中的行,比如使用>或<查找東西時發生的情況

  index 
這個連接類型對前面的表中的每一個記錄聯合進行完全掃描(比ALL更好,因爲索引一般小於表數據)

  ALL 
這個連接類型對於前面的每一個記錄聯合進行完全掃描,這一般比較糟糕,應該儘量避免



6)mysql不同掃描方式分析

(下面的數據來自掃描少量數據)

wKiom1V3mjaTAx8mAADf_Z2pRW4612.jpg


wKiom1V3mjWwc_kJAAFCAAmODl0620.jpg

  查詢是否存在緩存,打開表及鎖表這些操作時間是差不多,我們不會計入。具體還是看init,optimizing等環節消耗的時間。

  1.從這個表中,我們看到非主鍵索引和覆蓋索引在準備時間上需要開銷很多的時間,預估這兩種查詢方式都需要進行回表操作,所以花在準備上更多時間。


  2.第二項optimizing上,可以清晰知道,覆蓋索引話在優化上大量的時間,這樣在二級索引上就無需回表。


  3. Sendingdata,全表掃描慢就慢在這一項上,因爲是加載所有的數據頁,所以花費在這塊上時間較大,其他三者都差不多。


  4. 非主鍵查詢話在freeingitems上時間最少,那麼可以看出它在讀取數據塊的時候最少。


  5.相比較主鍵查詢和非主鍵查詢,非主鍵查詢在Init,statistics都遠高於主鍵查詢,只是在freeingitems開銷時間比主鍵查詢少。因爲這裏測試數據比較少,但是我們可以預見在大數據量的查詢上,不走緩存的話,那麼主鍵查詢的速度是要快於非主鍵查詢的,本次數據不過是太小體現不出差距而已。

  6.在大多數情況下,全表掃描還是要慢於索引掃描的。


  tips:


  過程中的輔助命令:


  1.清楚緩存


  reset query cache ;


  flush tables;


  2.查看錶的索引:


  show index from tablename;


7)Mysql5.6裏的index_condition_pushdown指的是在存儲器引擎層面先過濾了信息,在進行sql語句裏的具體查詢等工作,有時可能會比主鍵掃描等更慢。


8)semi-consistent read(僅僅針對於update操作):是read committed與consistent read兩者的結合。一個update語句,如果讀到一行已經加鎖的記錄,此時InnoDB返回記錄最近提交的版本,由MySQL上層判斷此版本是否滿足update的where條件。若滿足(需要更新),則MySQL會重新發起一次讀操作,此時會讀取行的最新版本(並加鎖)。

semi-consistent read只會發生在read committed隔離級別下,或者是參數innodb_locks_unsafe_for_binlog被設置爲true。


semi-consistent優缺點分析

優點

  • 減少了更新同一行記錄時的衝突,減少鎖等待。

    無併發衝突,讀記錄最新版本並加鎖;有併發衝突,讀事務最新的commit版本,不加鎖,無需鎖等待。

  • 可以提前放鎖,進一步減少併發衝突概率。

    對於不滿足update更新條件的記錄,可以提前放鎖,減少併發衝突的概率。

  • 在理解了semi-consistent read原理及實現方案的基礎上,可以酌情考慮使用semi-consistent read,提高系統的併發性能。

缺點

  • 非衝突串行化策略,因此對於binlog來說,是不安全的

    兩條語句,根據執行順序與提交順序的不同,通過binlog複製到備庫後的結果也會不同。不是完全的衝突串行化結果。

    因此只能在事務的隔離級別爲read committed(或以下),或者設置了innodb_locks_unsafe_for_binlog參數的情況下才能夠使用。

9)產生死鎖的條件:

(1) 互斥條件:一個資源每次只能被一個進程使用。

(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。

(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。

(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係


(四)說說MYSQL中具體的問題


MyISAM的鎖問題

併發插入(Concurrent Inserts)

上文提到過MyISAM表的讀和寫是串行的,但這是就總體而言的。在一定條件下,MyISAM表也支持查詢和插入操作的併發進行。

MyISAM存儲引擎有一個系統變量concurrent_insert,專門用以控制其併發插入的行爲,其值分別可以爲0、1或2。l        

 當concurrent_insert設置爲0時,不允許併發插入。


 當concurrent_insert設置爲1時,如果MyISAM表中沒有空洞(即表的中間沒有被刪除的行),MyISAM允許在一個進程讀表的同時,另一個進程從表尾插入記錄。這也是MySQL的默認設置。


 當concurrent_insert設置爲2時,無論MyISAM表中有沒有空洞,都允許在表尾併發插入記錄。


優先級以及調度

寫進程永遠在讀進程前


如何調度

 1)通過指定啓動參數low-priority-updates,使MyISAM引擎默認給予讀請求以優先的權利。

 2)通過執行命令SET LOW_PRIORITY_UPDATES=1,使該連接發出的更新請求優先級降低。

 3)通過指定INSERT、UPDATE、DELETE語句的LOW_PRIORITY屬性,降低該語句的優先級。

雖然上面3種方法都是要麼更新優先,要麼查詢優先的方法,但還是可以用其來解決查詢相對重要的應用(如用戶登錄系統)中,讀鎖等待嚴重的問題。

另外,MySQL也提供了一種折中的辦法來調節讀寫衝突,即給系統參數max_write_lock_count設置一個合適的值,當一個表的讀鎖達到這個值後,MySQL就暫時將寫請求的優先級降低,給讀進程一定獲得鎖的機會。



InnoDB鎖問題

innodb行鎖分類

record lock:記錄鎖,也就是僅僅鎖着單獨的一行

gap lock:區間鎖,僅僅鎖住一個區間(注意這裏的區間都是開區間,也就是不包括邊界值。

next-key lock:record lock+gap lock,所以next-key lock也就半開半閉區間,且是下界開,上界閉。

next-key 鎖定範圍:(負無窮大,最小第一記錄],(記錄之間],(最大記錄,正無窮大)


IX,IS爲表鎖,一下爲這四種鎖的衝突情況。

wKioL1V3pA-g3gg9AACS-kDeJbQ630.jpg

1)併發事務處理帶來的問題

相對於串行處理來說,併發事務處理能大大增加數據庫資源的利用率,提高數據庫系統的事務吞吐量,從而可以支持更多的用戶。但併發事務處理也會帶來一些問題,主要包括以下幾種情況。

更新丟失(lost update):當兩個或多個事務選擇同一行,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最後的更新覆蓋了由其他事務所做的更新。例如,兩個編輯人員製作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。

髒讀 就是讀取未提交的數據,根據未提交的數據而進一步做相應的操作會發生錯誤。

 e.g.
        1.Mary的原工資爲1000, 財務人員將Mary的工資改爲了8000(但未提交事務)
        2.Mary讀取自己的工資 ,發現自己的工資變爲了8000,歡天喜地!
        3.而財務發現操作有誤,回滾了事務,Mary的工資又變爲了1000
          像這樣,Mary記取的工資數8000是一個髒數據。

 

 不可重複讀 重點在修改內容,兩次讀取的內容不一樣。是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據,那麼兩次讀取到的內容就可能不一樣。

    e.g.
    1.在事務1中,Mary 讀取了自己的工資爲1000,操作並沒有完成
    2.在事務2中,這時財務人員修改了Mary的工資爲2000,並提交了事務.
    3.在事務1中,Mary 再次讀取自己的工資時,工資變爲了2000

 解決辦法:如果只有在修改事務完全提交之後纔可以讀取數據,則可以避免該問題。

 

 幻讀 :重點在增加、刪除,兩次讀取到的記錄數不一樣。 是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,以後就會發生事務一兩次讀取到了兩個不同的數目。
   e.g. 
   目前工資爲1000的員工有10人。
   1.事務1,讀取所有工資爲1000的員工。
   2.這時事務2向employee表插入了一條員工記錄,工資也爲1000
   3.事務1再次讀取所有工資爲1000的員工 共讀取到了11條記錄

2)innodb鎖爭用解決

通過設置InnoDB Monitors來進一步觀察發生鎖衝突的表、數據行等,並分析鎖爭用的原因。

具體方法如下:

mysql> CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;

Query OK, 0 rows affected (0.14 sec)

然後就可以用下面的語句來進行查看:

mysql> Show innodb status\G;


監視器可以通過發出下列語句來停止查看:

mysql> DROP TABLE innodb_monitor;

Query OK, 0 rows affected (0.05 sec)

設置監視器後,在SHOW INNODB STATUS的顯示內容中,會有詳細的當前鎖等待的信息,包括表名、鎖類型、鎖定記錄的情況等,便於進行進一步的分析和問題的確定。打開監視器以後,默認情況下每15秒會向日志中記錄監控的內容,如果長時間打開會導致.err文件變得非常的巨大,所以用戶在確認問題原因之後,要記得刪除監控表以關閉監視器,或者通過使用“--console”選項來啓動服務器以關閉寫日誌文件。

InnoDB行鎖實現方式

InnoDB行鎖是通過給索引上的索引項加鎖來實現的。只有通過索引條件檢索數據,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!



SQL 標準中定義了 4 個隔離級別 read uncommited , read commited , repeatable read , serializable 。

read uncommited 即髒讀,一個事務修改了一行,另一個事務也可以讀到該行。如果第一個事務執行了回滾,那麼第二個事務讀取的就是從來沒有正式出現過的值。 

read commited 即一致讀,試圖通過只讀取提交的值的方式來解決髒讀的問題,但是這又引起了不可重複讀取的問題。

repeatable read 即可重複讀,在一個事務對數據行執行讀取或寫入操作時鎖定了這些數據行。(innodb默認)

serializable即可串行操作:在數據表上放置了排他鎖,以防止在事務完成之前由其他用戶更新行或向數據集中插入行,這是最嚴格的鎖。它防止了髒讀、不可重複讀取和幻象數據。但是因爲innodb爲並行操作所以不建議用此模式。














































發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章