MySQL——事務、鎖機制

一、事務(一組特定sql語句的集合)

(1)由於 MyISAM 不支持事務,所以事務是針對於 InnoDB 來說;

(2)事務處理可以用來維護數據庫的完整性,保證成批的 SQL 語句要麼全部執行,要麼全部不執行;

(3)事務用來管理 insert,update,delete 語句;

事務的特性:A:原子性(Atomicity,或稱不可分割性)     C:一致性(Consistency)    I:隔離性(Isolation,又稱獨立性)    D:持久性(Durability)

(1)原子性:一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

原子性通過日誌系統來確保:

(2)一致性:在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續數據庫可以自發性地完成預定的工作。用鎖機制來進行確保。

(3)隔離性:數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和可序列化(串行化)(Serializable)

 MySQL/InnoDB默認是可重複讀的(REPEATABLE READ);

Oracle默認隔離級別是讀已提交(READ_COMMITTED);

查看隔離級別:select @@tx_isolation;

#查全局事務隔離級別
mysql> select @@global.tx_isolation;
#查當前會話事務隔離級別
mysql> select @@session.tx_isolation; 
#查當前事務隔離級別
mysql> select @@tx_isolation;

修改隔離級別:set tx_isolation = "具體的隔離級別(四種)";

#設置會話級別
mysql> set session tx_isolation='read-uncommitted';   
#設置全局級別
mysql> set global tx_isolation='read-uncommitted';  

設置隔離級別作用的範圍:


#事務隔離級別的作用範圍分爲兩種:
#--全局級:對所有的會話有效
#--會話級:只對當前的會話有效
#例如,設置會話級隔離級別爲READ COMMITTED 
mysql> set transaction isolation level READ COMMITTED;
或:
mysql> set session transaction isolation level READ COMMITTED;
#設置全局級隔離級別爲READ COMMITTED
mysql> set global transaction isolation level READ COMMITTED;

未提交讀:

- 事物A和事物B,事物A未提交的數據,事物B可以讀取到
- 這裏讀取到的數據叫做“髒數據”
- 這種隔離級別最低,這種級別一般是在理論上存在,數據庫隔離級別一般都高於該級別

已提交讀:

- 事物A和事物B,事物A提交的數據,事物B才能讀取到
- 這種隔離級別高於讀未提交
- 換句話說,對方事物提交之後的數據,我當前事物才能讀取到
- 這種級別可以避免“髒數據”
- 這種隔離級別會導致“不可重複讀取”
- Oracle默認隔離級別

可重複讀:

- 事務A和事務B,事務A提交之後的數據,事務B讀取不到
- 事務B是可重複讀取數據
- 這種隔離級別高於讀已提交
- 換句話說,對方提交之後的數據,我還是讀取不到
- 這種隔離級別可以避免“不可重複讀取”,達到可重複讀取
- 比如1點和2點讀到數據是同一個
- MySQL默認級別
- 雖然可以達到可重複讀取,但是會導致“幻讀”

可序列化(串行化):

- 事務A和事務B,事務A在操作數據庫時,事務B只能排隊等待
- 這種隔離級別很少使用,吞吐量太低,用戶體驗差
- 這種級別可以避免“幻讀”,每一次讀取的都是數據庫中真實存在數據,事務A與事務B串行,而不併發

  • 更新丟失(Lost Update):當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題——最後的更新覆蓋了其他事務所做的更新。例如,兩個編輯人員製作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然後保存更改後的副本,這樣就覆蓋了原始文檔。最後保存其更改保存其更改副本的編輯人員覆蓋另一個編輯人員所做的修改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。
  • 髒讀(Dirty Reads):一個事務正在對一條記錄做修改,在這個事務並提交前,這條記錄的數據就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“髒”的數據,並據此做進一步的處理,就會產生未提交的數據依賴關係。這種現象被形象地叫做“髒讀”。
  • 不可重複讀(Non-Repeatable Reads):一個事務在讀取某些數據已經發生了改變、或某些記錄已經被刪除了!這種現象叫做“不可重複讀”。
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的數據,卻發現其他事務插入了滿足其查詢條件的新數據,這種現象就稱爲“幻讀”

(4)持久性:事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。也是通過日誌系統來進行確保。

在 MySQL 命令行的默認設置下,事務都是自動提交的,即執行 SQL 語句後就會馬上執行 COMMIT 操作。因此要顯式地開啓一個事務務須使用命令 begin 或 start transaction,或者執行命令 set autocommit = 0,用來禁止使用當前會話的自動提交。

MYSQL 事務處理主要有兩種方法:

1、用 BEGIN, ROLLBACK, COMMIT來實現

  • BEGIN 開始一個事務
  • ROLLBACK 事務回滾
  • COMMIT 事務確認

2、直接用 SET 來改變 MySQL 的自動提交模式:

  • SET AUTOCOMMIT=0 禁止自動提交
  • SET AUTOCOMMIT=1 開啓自動提交

事務控制語句:

begin 或 start transaction顯式地開啓一個事務;

commit 也可以使用 commit work,不過二者是等價的。commit會提交事務,並使已對數據庫進行的所有修改成爲永久性的;

rollback也可以使用roback work,不過二者是等價的。回滾會結束用戶的事務,並撤銷正在進行的所有未提交的修改;

savepoint identifier,savepoint允許在事務中創建一個保存點,一個事務中可以有多個savepoint;

release savepoint identifier 刪除一個事務的保存點,當沒有指定的保存點時,執行該語句會拋出一個異常;

rollback to identifier 把事務回滾到標記點;

set transaction用來設置事務的隔離級別。InnoDB 存儲引擎提供事務的隔離級別有:讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable);

mysql> use RUNOOB;
Database changed
mysql> CREATE TABLE runoob_transaction_test( id int(5)) engine=innodb;  # 創建數據表
Query OK, 0 rows affected (0.04 sec)
 
mysql> select * from runoob_transaction_test;
Empty set (0.01 sec)
 
mysql> begin;  # 開始事務
Query OK, 0 rows affected (0.00 sec)
 
mysql> insert into runoob_transaction_test value(5);
Query OK, 1 rows affected (0.01 sec)
 
mysql> insert into runoob_transaction_test value(6);
Query OK, 1 rows affected (0.00 sec)
 
mysql> commit; # 提交事務
Query OK, 0 rows affected (0.01 sec)
 
mysql>  select * from runoob_transaction_test;
+------+
| id   |
+------+
| 5    |
| 6    |
+------+
2 rows in set (0.01 sec)
 
mysql> begin;    # 開始事務
Query OK, 0 rows affected (0.00 sec)
 
mysql>  insert into runoob_transaction_test values(7);
Query OK, 1 rows affected (0.00 sec)
 
mysql> rollback;   # 回滾
Query OK, 0 rows affected (0.00 sec)
 
mysql>   select * from runoob_transaction_test;   # 因爲回滾所以數據沒有插入
+------+
| id   |
+------+
| 5    |
| 6    |
+------+
2 rows in set (0.01 sec)
 
mysql>

二、鎖機制

定義:當有事務操作時,數據庫引擎會要求不同類型的鎖定,如相關數據行、數據頁或是整個數據表,當鎖定運行時,會阻止其他事務對已經鎖定的數據行、數據頁或數據表進行操作。只有在當前事務對於自己鎖定的資源不在需要時,纔會釋放其鎖定的資源,供其他事務使用。

MyISAM和MEMORY存儲引擎採用的是表級鎖(table-level locking);

BDB存儲引擎採用的是頁面鎖(page-level locking),但也支持表級鎖;

InnoDB存儲引擎既支持行級鎖(row-level locking),也支持表級鎖,但默認情況下是採用行級鎖。 

表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。 
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。 
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般 

下面以MyISAM和InnoDB來進行說明:

(1)MySQL的MyISAM表級鎖有兩種模式:共享讀鎖(Table Read Lock)獨佔寫鎖(Table Write Lock)。 
共享讀鎖:對MyISAM表的讀操作,不會阻塞其他用戶對同一表的讀請求,但會阻塞對同一表的寫請求;

獨佔寫鎖:對 MyISAM表的寫操作,則會阻塞其他用戶對同一表的讀和寫操作;MyISAM表的讀操作與寫操作之間,以及寫操作之間是串行的!當一個線程獲得對一個表的寫鎖後,只有持有鎖的線程可以對錶進行更新操作。其他線程的讀、寫操作都會等待,直到鎖被釋放爲止。

(2)InnoDB實現了以下兩種類型的行鎖:

共享鎖(S):又稱讀鎖。允許一個事務去讀一行,阻止其他事務獲得相同數據集的排他鎖。若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。

排他鎖(X):又稱寫鎖。允許獲取排他鎖的事務更新數據,阻止其他事務取得相同的數據集共享讀鎖和排他寫鎖。若事務T對數據對象A加上X鎖,事務T可以讀A也可以修改A,其他事務不能再對A加任何鎖,直到T釋放A上的鎖。

對於共享鎖大家可能很好理解,就是多個事務只能讀數據不能改數據。 
排他鎖指的是一個事務在一行數據加上排他鎖後,其他事務不能再在其上加其他的鎖。mysql InnoDB引擎默認的修改數據語句:update,delete,insert都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型,如果加排他鎖可以使用select …for update語句,加共享鎖可以使用select … lock in share mode語句。所以加過排他鎖的數據行在其他事務種是不能修改數據的,也不能通過for update和lock in share mode鎖的方式查詢數據,但可以直接通過select …from…查詢數據,因爲普通查詢沒有任何鎖機制。

爲了允許行鎖和表鎖共存,實現多粒度鎖機制,InnoDB還有兩種內部使用的意向鎖(Intention Locks),這兩種意向鎖都是表鎖。

意向共享鎖(IS):事務打算給數據行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖。

意向排他鎖(IX):事務打算給數據行加排他鎖,事務在給一個數據行加排他鎖前必須先取得該表的IX鎖。

如果一個事務請求的鎖模式與當前的鎖兼容,InnoDB就請求的鎖授予該事務;反之,如果兩者兩者不兼容,該事務就要等待鎖釋放。 
意向鎖是InnoDB自動加的,不需用戶干預。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及數據集加排他鎖;對於普通SELECT語句,InnoDB不會加任何鎖。 

意向鎖:意向鎖的含義是如果對一個結點加意向鎖,則說明該結點的下層結點正在被加鎖;對任一結點加鎖時,必須先對它的上層結點加意向鎖。如:對錶中的任一行加鎖時,必須先對它所在的表加意向鎖,然後再對該行加鎖。這樣一來,事務對錶加鎖時,就不再需要檢查表中每行記錄的鎖標誌位了,系統效率得以大大提高。簡單的說就是我要對哪個表進行事務操作了,就給哪個表加一個意向鎖。

悲觀鎖(Pessimistic Lock):每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

樂觀鎖(Optimistic Lock):每次去拿數據的時候都認爲別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。

兩種鎖各有優缺點,不可認爲一種好於另一種,像樂觀鎖適用於寫比較少的情況下,即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生衝突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。

間隙鎖:當我們用範圍條件而不是相等條件檢索數據,並請求共享或排他鎖時,InnoDB會給符合條件的已有數據記錄的 索引項加鎖;對於鍵值在條件範圍內但並不存在的記錄,叫做“間隙(GAP)”,InnoDB也會對這個“間隙”加鎖,這種鎖機制就是所謂的間隙鎖 (Next-Key鎖)。 InnoDB使用間隙鎖的目的,一方面是爲了防止幻讀,以滿足相關隔離級別的要求;另外一方面,是爲了滿足其恢復和複製的需要。

 

 

 

 

 

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