MySql中的事務

Mysql數據庫是咱們在開發中經常使用的一個開源數據庫,自2001年MySql開始引入InnoDB存儲引擎,並在以後一年宣佈全面支持事務以來,Mysql的發展進入快車道;Mysql其優良的性能與可靠性,支持它開源數據庫中獨佔鰲頭。同時,MySql也有完善,安全的事務控制。html

1.存儲引擎的選擇

同大多數數據庫同樣,Mysql中有一個存儲引擎的概念,針對不一樣的存儲需求能夠選擇最優的存儲引擎 Mysql默認支持多種存儲引擎,5.0後,包括:MyISAM、InnoDB、MEMORY、MERGE、BDB等,其中InnoDB和BDB提供事務安全表,其餘存儲引擎都是非事務安全表;

這裏我講的是事務的控制,因此咱們在建立表的時候,都是選擇的InnoDB存儲引擎,它也是MySql5.5以後的默認存儲引擎mysql

2.事務的概念

事務指邏輯上的一組操做,組成這組操做的各個單元,要不所有成功,要不所有不成功。 也就是下面事務的原子性 這裏咱們一次銀行的轉帳爲例:A——B轉賬,對應於以下兩條sql語句
sql

update from account set money=money+100 where name='B';
update from account set money=money-100 where name='A';
複製代碼

每條sql語句能夠看作一個獨立的單元,這兩條語句只能所有執行完成才能算此次事務執行完成數據庫

3.事務的四大特性(ACID)

3.1 原子性(Atomicity

  原子性是指事務是一個不可分割的工做單位,事務中的操做要麼所有成功,要麼所有失敗。好比在同一個事務中的SQL語句,要麼所有執行成功,要麼所有執行失敗
安全

3.2 一致性(Consistency)

  官網上事務一致性的概念是:事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態。以轉帳爲例子,A向B轉帳,假設轉帳以前這兩個用戶的錢加起來總共是2000,那麼A向B轉帳以後,無論這兩個帳戶怎麼轉,A用戶的錢和B用戶的錢加起來的總額仍是2000,這個就是事務的一致性。
bash

3.3 隔離性(Isolation)

  事務的隔離性是多個用戶併發訪問數據庫時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做數據所幹擾,多個併發事務之間要相互隔離。
併發

3.4 持久性(Durability)

  持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即便數據庫發生故障也不該該對其有任何影響
 事務的四大特性中最麻煩的是隔離性,也是咱們事務控制的主要內容  性能

 4.MySql數據庫中操做事務的命令

默認狀況下,MySql事務是自動提交(Autocommit)的,若果須要明確的Commit和Rollback來提交和回滾事務,那麼就須要明確的事務控制命令來開始事務。
SET AUTOCOMMIT能夠修改當前鏈接的提交方式,若是設置了set autocommit=0,則設置以後的全部事務都須要經過明確的命令進行提交或者回滾。若是隻是對某些語句須要進行事務控制,則使用start transaction語句開始一個事務比較方便,這樣事務結束以後能夠自動回到自動提交的方式,若是但願全部的事務都不是自動提交的,那麼經過修改AUTOCOMMIT來控制事務比較方便,這樣不用在每一個事務開始的時候再執行start transaction.測試

4.1. 建立測試sql腳本
//建立帳戶表
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(40) DEFAULT NULL,
  `money` float DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

//插入測試數據
INSERT INTO `account` VALUES ('1', 'A', '1000');
INSERT INTO `account` VALUES ('2', 'B', '1000');
INSERT INTO `account` VALUES ('3', 'C', '1000');


複製代碼
4.2 開啓事務(start transaction)

使用"start transaction"開啓MySQL數據庫的事務,以下所示:ui

mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1000 |
|  2 | B    |  1000 |
|  3 | C    |  1000 |
+----+------+-------+
3 rows in set (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
複製代碼

咱們首先在數據庫中模擬轉帳失敗的場景,首先執行update語句讓A用戶的money減小100塊錢,以下所示:

mysql> update account set money=money-100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql>  select * from account where name='A';
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |   900 |
+----+------+-------+
1 row in set (0.00 sec)
複製代碼

而後咱們關閉當前操做的dos命令行窗口,這樣就致使了剛纔執行的update語句的數據庫的事務沒有被提交,那麼咱們對A用戶的修改就不算是是真正的修改了,下次在查詢A用戶的money時,依然仍是以前的1000,以下所示:

mysql> select * from account where name='A';
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1000 |
+----+------+-------+
1 row in set (0.01 sec)
複製代碼
4.3 提交事務

 下面咱們在數據庫模擬A——B轉帳成功的場景:  

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

//讓兩個更新操做在同一事務中進行
mysql> update account set money=money-100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update account set money=money+100 where name='B';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
//提交事務
mysql> commit;
Query OK, 0 rows affected (0.03 sec)

//轉帳完成
mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |   900 |
|  2 | B    |  1100 |
|  3 | C    |  1000 |
+----+------+-------+
3 rows in set (0.00 sec)
複製代碼

在事務提交後,A——B轉帳100塊錢的這個業務操做算是真正成功了,A帳戶中少了100,B帳戶中多了100。

4.4 回滾事務(rollback)

經過手動回滾事務,使前面執行的操做無效

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

//對A,B進行兩次update操做
mysql> update account set money=money-100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update account set money=money+100 where name='B';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update account set money=money-100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update account set money=money+100 where name='B';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from account where name in('A','B');
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |   700 |
|  2 | B    |  1300 |
+----+------+-------+
2 rows in set (0.00 sec)

//手動回滾事務,使前面的update操做無效
mysql> rollback;
Query OK, 0 rows affected (0.03 sec)

//A帳戶,B帳戶回到事務開始前狀態
mysql> select * from account where name in('A','B');
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |   900 |
|  2 | B    |  1100 |
+----+------+-------+
2 rows in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)
複製代碼

過手動回滾事務,讓全部的操做都失效,這樣數據就會回到最初的初始狀態。
在事務中能夠經過定義SAVEPOINT,指定回滾事務的一個部分,語法:

mysql> savepoint test;
Query OK, 0 rows affected (0.00 sec)
複製代碼

回滾時,就能夠指定回滾點:

mysql> rollback to savepoint test;
Query OK, 0 rows affected (0.00 sec)
複製代碼

5.事務的隔離級別

 多個線程開啓各自事務操做數據庫中數據時,數據庫系統要負責隔離操做,以保證各個線程在獲取數據時的準確性。

 5.一、事務不考慮隔離性可能會引起的問題  

  若是事務不考慮隔離性,可能會引起以下問題:

  一、髒讀

髒讀指一個事務讀取了另一個事務未提交的數據。
這是很是危險的,假設A向B轉賬100元,對應sql語句以下所示    

1.update account set money=money+100 where name='B';    
2.update account set money=money-100  where name='A 複製代碼

當第1條sql執行完,第2條還沒執行(A未提交時),若是此時B查詢本身的賬戶,就會發現本身多了100元錢。若是A等B走後再回滾,若是B再查詢帳戶發現100元並無到帳,B就會損失100元。  

  二、不可重複讀

不可重複讀指在一個事務內讀取表中的某一行數據,屢次讀取結果不一樣。
  例如銀行想查詢A賬戶餘額,第一次查詢A賬戶爲200元,此時A向賬戶內存了100元並提交了,銀行接着又進行了一次查詢,此時A賬戶爲300元了。銀行兩次查詢不一致,可能就會很困惑,不知道哪次查詢是準的。
不可重複讀和髒讀的區別是,髒讀是讀取前一事務未提交的髒數據,不可重複讀是從新讀取了前一事務已提交的數據
  不少人認爲這種狀況就對了,無須困惑,固然是後面的爲準。咱們能夠考慮這樣一種狀況,好比銀行程序須要將查詢結果分別輸出到電腦屏幕和寫到文件中,結果在一個事務中針對輸出的目的地,進行的兩次查詢不一致,致使文件和屏幕中的結果不一致,銀行工做人員就不知道以哪一個爲準了。

  三、虛讀(幻讀)

  虛讀(幻讀)是指在一個事務內讀取到了別的事務插入的數據,致使先後讀取不一致。
  如丙存款100元未提交,這時銀行作報表統計account表中全部用戶的總額爲500元,而後丙提交了,這時銀行再統計發現賬戶爲600元了,形成虛讀一樣會使銀行不知所措,到底以哪一個爲準。

不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表

5.二、事務隔離性的設置語句

MySQL數據庫共定義了四種隔離級別:

  • Serializable(串行化):可避免髒讀、不可重複讀、虛讀狀況的發生。
  • Repeatable read(可重複讀):可避免髒讀、不可重複讀狀況的發生。
  • Read committed(讀已提交):可避免髒讀狀況發生。
  • Read uncommitted(讀未提交):最低級別,以上狀況均沒法保證。

mysql數據庫查詢當前事務隔離級別:select @@tx_isolation

例如:
mysql> select @@tx_isolation
    -> ;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
複製代碼

mysql數據庫默認的事務隔離級別是:Repeatable read(可重複讀)
mysql數據庫設置事務隔離級別:set transaction isolation level 隔離級別名

mysql> set transaction isolation level Read uncommitted;
Query OK, 0 rows affected (0.00 sec)
複製代碼
5.三、使用MySQL數據庫演示不一樣隔離級別下的併發問題

同時打開兩個窗口模擬2個用戶併發訪問數據庫

一、當把事務的隔離級別設置爲read uncommitted時,會引起髒讀、不可重複讀和虛讀

A窗口

//設置隔離級別爲未提交
mysql> set transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

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

mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1000 |
|  2 | B    |  1000 |
|  3 | C    |  1000 |
+----+------+-------+
3 rows in set (0.01 sec)

//這裏讀了B事務未提交的髒數據
mysql> select * from account;
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1100 |
|  2 | B    |  1000 |
|  3 | C    |  1000 |
+----+------+-------+
3 rows in set (0.00 sec)
複製代碼

B窗口   

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

mysql> update account set money=money+100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
//這裏不要提交,到A窗口查詢A帳戶
mysql>
複製代碼

二、當把事務的隔離級別設置爲read committed時,會引起不可重複讀和虛讀,但避免了髒讀

A窗口    

set transaction isolation level  read committed;
start transaction;
select * from account;--發現a賬戶是1000元,轉到b窗口
select * from account; --發現a賬戶多了100,這時候,a讀到了別的事務提交的數據,兩次讀取a賬戶讀到的是不一樣的結果(不可重複讀)
複製代碼

B窗口    

start transaction;
update account set money=money+100 where name='aaa';
 commit;--轉到a窗口
複製代碼

三、當把事務的隔離級別設置爲repeatable read(mysql默認級別)時,會引起虛讀,但避免了髒讀、不可重複讀 mysql在執行select操做時,也能夠避免幻讀,可是在執行update、delete、insert操做時,仍然會有幻讀
MySql在MVCC併發控制中,讀操做能夠分紅兩類:快照讀 (snapshot read)與當前讀 (current read)
具體可看:www.cnblogs.com/chinesern/p…
具體看個例子:
A窗口:

mysql> start transaction; --使用默認隔離級別 repeatable read
Query OK, 0 rows affected (0.00 sec)

//A帳戶初始狀態
mysql> select * from account where name='A';
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1000 |
+----+------+-------+
1 row in set (0.00 sec)

//B事務提交後再次查看,A帳戶未發生變化,避免了不可重複讀
mysql> select * from account where name='A';
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1000 |
+----+------+-------+
1 row in set (0.00 sec)

mysql> update account set money=money+100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
//這裏只更新了一次,可是結果卻不一致,引起了幻讀
mysql> select * from account where name='A';
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1200 |
+----+------+-------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.03 sec)

mysql> select * from account where name='A';
+----+------+-------+
| id | name | money |
+----+------+-------+
|  1 | A    |  1200 |
+----+------+-------+
1 row in set (0.00 sec)

mysql>
複製代碼

B窗口:

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

//修改A帳戶
mysql> update account set money=money+100 where name='A';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> commit;
Query OK, 0 rows affected (0.13 sec)
//提交後轉到A窗口查看
mysql>
複製代碼

四、當把事務的隔離級別設置爲Serializable時,會避免全部問題

A窗口    

set transaction isolation level Serializable;
start transaction;
select * from account;--轉到b窗口
複製代碼

B窗口    

start transaction;
insert into account(name,money) values('ggg',1000);--發現不能插入,只能等待a結束事務才能插入
複製代碼

在選擇事務隔離級別時,通常不推薦使用串行化,一是默認的repeatable read以能應付大多數併發狀況,二是使用串行化會加大系統開銷,執行效率大大下降

相關文章
相關標籤/搜索