數據庫——MySQL——事務

數據的事務是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底執行,要麼徹底不執行。html

事務必須具有四個特性:mysql

  • 原子性
    • 原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾
  • 一致性
    • 在事務T開始時,此時數據庫有一種狀態,這個狀態是全部的MySQL對象處於一致的狀態,例如數據庫完整性約束正確,日誌狀態一致等,當事務T提交後,這時數據庫又有了一個新的狀態,不一樣的數據,不一樣的索引,不一樣的日誌等,但此時,約束,數據,索引,日誌等MySQL各類對象仍是要保持一致性(正確性)。 這就是 從一個一致性的狀態,變到另外一個一致性的狀態。也就是事務執行後,並無破壞數據庫的完整性約束(一切都是對的)。
  • 隔離性
    • 隔離性是指當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。換句話來講就是每一個事務都感受不到有其餘事務在併發地執行。
  • 持久性
    • 持久性是指一個事務一旦被提交了,那麼對於數據庫中的數據改變就是永久性的。

MySQL使用事務

提交事務

>set autocommit = 0 禁止自動提交
>start transaction;
>update accout set money=money+100 where name="Jason";
>commit;

回滾事務

>set autocommit = 0 禁止自動提交
>start transaction;
>update account set money=money-100 where name="justin";
>rollback;

關於事務隔離

若是沒有隔離會發生這樣幾個問題:算法

髒讀

一個事務處理過程裏讀取了另外一個未說起的的事務中的數據。sql

不可重複讀

對於數據庫中的某個數據,一個事務範圍內屢次查詢卻能夠返回不一樣的數據值,這是因爲在查詢的間隔期間,另一個事務修改並提交了該數據。數據庫

幻讀

在一個事務中讀取到了別的事務插入的數據,致使先後不同。好比:第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的所有數據行。同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那麼,之後就會發生操做第一個事務的用戶發現表中還有沒有修改的數據行,就好象發生了幻覺同樣。segmentfault

關於隔離級別分爲四個等級

查看當前事務級別session

select @@tx_isolation;

修改事務的隔離級別併發

語法ide

set  [global | session]  transaction isolation level 隔離級別名稱;

 例如:高併發

set global transaction isolation level Repeatable read;
# 修改全局的以後要從新創建會話纔有效

 隔離級別:Serializable | Repeatable read | Read committed |Read uncommitted

注意:
設置默認級別是指當前session的下一個事務
設置session級別是指當前session之後的全部事務
設置global級別是指對以後的全部session,不包括當前session

推薦博客:http://blog.chinaunix.net/uid-14010457-id-3956842.html

 MySQL中的鎖機制

數據庫爲了保證四個特性,特別是一致性和隔離性,採用了加鎖的方式。因爲數據庫是一個高併發的應用,同一時間有大量的併發訪問。若是加鎖過分的話,會極大地下降併發處理能力,因此對於加鎖的處理,是數據庫對於事務處理的精髓所在。

鎖方案

一次封鎖

由於有大量的併發訪問,爲了預防死鎖,通常應用中採用的是一次封鎖的方案:就是在方法的開始階段,已經預先知道須要用到那些數據,而後所有鎖住,在方法執行以後,再所有解鎖。
這種方案能夠有效避免死鎖發生,當時因爲數據庫操做在事務開始階段並不知道具體會用到哪些數據,因此該方案不合適在數據庫中使用。

兩段鎖

兩段鎖協議將事務分紅兩個階段:加鎖階段和解鎖階段

  • 加鎖階段:在該階段能夠進行加鎖操做。讀數據前須要申請獲取S鎖(共享鎖:其餘事務能夠繼續加共享鎖,但不能加排他鎖);寫數據前須要申請獲取X鎖(排他鎖:其餘事務不能獲取任何鎖)。加鎖不成功,則事務進入等待狀態,直到加鎖成功才繼續執行。
  • 解鎖階段:當事務釋放了一個封鎖之後,事務進入解鎖階段,在該階段只能進行解鎖操做不能進行加鎖操做。

注:這個方案沒法避免死鎖,可是能夠保證事務調度的串行化(串行化在數據庫恢復和備份時候很重要)。

MySQL的鎖類型

表鎖

對一整張表加鎖,併發能力低下(即便有分讀鎖、寫鎖),通常在DDL處理時使用

innodb中的鎖

InnoDB是一個支持行鎖的存儲引擎,鎖的類型有:共享鎖(S)、排他鎖(X)、意向共享(IS)、意向排他(IX)。爲了提供更好的併發,InnoDB提供了非鎖定讀:不須要等待訪問行上的鎖釋放,讀取行的一個快照。該方法是經過InnoDB的一個特性:MVCC來實現的。
innodb有三種行鎖的算法:
  • 行鎖(record lock):單個記錄上鎖。
  • 間隙鎖(又叫GAP鎖或者Gap Lock):鎖定一個範圍,但不包括記錄自己。GAP鎖的目的,是爲了防止同一事務的兩次當前讀,出現幻讀的狀況。
  • Next-Key鎖():行鎖和間隙鎖的結合體。鎖定一個範圍,而且鎖定記錄自己。對於行的查詢,都是採用該方法,主要目的是解決幻讀的問題。

下圖時三個鎖的位置關係圖:

 

行鎖

只鎖住特定行的數據,併發能力強,MySQL通常都是用行鎖來處理併發事務。

若是用到無索引的字段,那麼MySQL會在存儲引擎層面將全部的記錄加鎖,而後由MySQL Server過濾,若是不知足會調用unlock_row把不知足條件的記錄釋放鎖(這裏違背了二段鎖協議)。
這種狀況一樣適用於MySQL的默認隔離級別RR。因此對一個數據量很大的表作批量修改的時候,若是沒法使用相應的索引,MySQL Server過濾數據的的時候特別慢,就會出現雖然沒有修改某些行的數據,可是它們仍是被鎖住了的現象。

GAP鎖和Next-Key鎖

# -----------------會話1---------------------------------------------------
mysql> create table t(id int,name varchar(10),key idx_id(id),primary key(name))engine =innodb;
Query OK, 0 rows affected (0.03 sec)

mysql> insert into t values(1,'a'),(3,'c'),(5,'e'),(8,'g'),(11,'j');   
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select @@global.tx_isolation, @@tx_isolation;    
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 2 warnings (0.01 sec)

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    3 | c    |
|    5 | e    |
|    8 | g    |
|   11 | j    |
+------+------+
5 rows in set (0.01 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> delete from t where id=8;
Query OK, 1 row affected (0.00 sec)
"""
能夠看到最後加了一個事務,對id=8的數據進行處理,然而沒有提交
"""



# -----------------會話2---------------------------------------------------
mysql> select @@global.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 2 warnings (0.00 sec)

mysql> start transaction; 
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t(id,name) values(6,'f');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(5,'e1');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(7,'h');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(8,'gg');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(9,'k');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(10,'p');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(11,'iz');
^C^C -- query aborted
ERROR 1317 (70100): Query execution was interrupted
mysql> insert into t(id,name) values(5,'cz');
Query OK, 1 row affected (0.00 sec)

mysql> insert into t(id,name) values(11,'ja');
Query OK, 1 row affected (0.00 sec)
"""
分析:由於會話1已經對id=8的記錄加了一個X鎖,因爲是RR隔離級別,INNODB要防止幻讀須要加GAP鎖:即id=5(8的左邊),id=11(8的右邊)之間須要加間隙鎖(GAP)。
這樣[5,e]和[8,g],[8,g]和[11,j]之間的數據都要被鎖。上面測試已經驗證了這一點,根據索引的有序性,數據按照主鍵(name)排序,後面寫入的[5,cz]([5,e]的左邊)
和[11,ja]([11,j]的右邊)不屬於上面的範圍從而能夠寫入。

另一種狀況,把name主鍵去掉會是怎麼樣的狀況?有興趣的能夠測試一下。
"""
例1

上面的例子中,當插入被會話1鎖住的內容的時候,會有一個超時間,我用ctrl+c強制終止了。若是是在一個事務中,插入數據的時候超時了,會怎麼辦呢。

超時時間的參數:innodb_lock_wait_timeout ,默認是50秒。
超時是否回滾參數:innodb_rollback_on_timeout 默認是OFF。

 在默認請求下,不會由於超時引起的異常而回滾,當參數innodb_rollback_on_timeout設置爲ON時,則會。

 接下來再看一個例子

# -----------------會話1---------------------------------------------------
mysql> create table tt(a int primary key)engine =innodb;
Query OK, 0 rows affected (0.06 sec)

mysql> insert into tt values(1),(3),(5),(8),(11);
Query OK, 5 rows affected (0.01 sec)
Records: 5  Duplicates: 0  Warnings: 0

mysql> select * from t;
+------+------+
| id   | name |
+------+------+
|    1 | a    |
|    3 | c    |
|    5 | e    |
|   11 | j    |
+------+------+
4 rows in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tt where a = 8 for update;
+---+
| a |
+---+
| 8 |
+---+
1 row in set (0.00 sec)
# -----------------會話2---------------------------------------------------
mysql> start transaction;
Query OK, 0 rows affected (0.01 sec)

mysql> insert into tt values(6);
Query OK, 1 row affected (0.01 sec)

mysql> insert into tt values(7);
Query OK, 1 row affected (0.00 sec)

mysql> insert into tt values(9);
Query OK, 1 row affected (0.00 sec)
例2

 到這裏應該會有些人有問題了,爲啥這個例2和例1不同。

 解釋:

由於InnoDB對於行的查詢都是採用了Next-Key Lock的算法,鎖定的不是單個值,而是一個範圍,按照這個方法是會和第一次測試結果同樣。可是,當查詢的索引含有惟一屬性的時候,Next-Key Lock 會進行優化,將其降級爲Record Lock,即僅鎖住索引自己,不是範圍。

注意:經過主鍵或則惟一索引來鎖定不存在的值,也會產生GAP鎖定。

 

若是不想出現那種阻塞的現象,能夠顯示的關閉GAP鎖

1:把事務隔離級別改爲:Read Committed,提交讀、不可重複讀。SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

2:修改參數:innodb_locks_unsafe_for_binlog 設置爲1。

注:innodb_locks_unsafe_for_binlog最主要的做用就是控制innodb是否對gap加鎖。該參數若是是enable的,則是unsafe的,此時gap不會加鎖;反之,若是disable掉該參數,則gap會加鎖。固然對於一些和數據完整性相關的定義,如外鍵和惟一索引(含主鍵)須要對gap進行加鎖,那麼innodb_locks_unsafe_for_binlog的設置並不會影響gap是否加鎖。

 

牛人博客推薦:http://hedengcheng.com/?p=771

 

MVVC(多版本併發控制)

關於這方面的知識推薦一個思否的文章:http://www.javashuo.com/article/p-nocfhqye-eu.html

相關文章
相關標籤/搜索