Mysql數據庫是咱們在開發中經常使用的一個開源數據庫,自2001年MySql開始引入InnoDB存儲引擎,並在以後一年宣佈全面支持事務以來,Mysql的發展進入快車道;Mysql其優良的性能與可靠性,支持它開源數據庫中獨佔鰲頭。同時,MySql也有完善,安全的事務控制。html
同大多數數據庫同樣,Mysql中有一個存儲引擎的概念,針對不一樣的存儲需求能夠選擇最優的存儲引擎 Mysql默認支持多種存儲引擎,5.0後,包括:MyISAM、InnoDB、MEMORY、MERGE、BDB等,其中InnoDB和BDB提供事務安全表,其餘存儲引擎都是非事務安全表;
這裏我講的是事務的控制,因此咱們在建立表的時候,都是選擇的InnoDB存儲引擎,它也是MySql5.5以後的默認存儲引擎mysql
事務指邏輯上的一組操做,組成這組操做的各個單元,要不所有成功,要不所有不成功。 也就是下面事務的原子性 這裏咱們一次銀行的轉帳爲例: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語句能夠看作一個獨立的單元,這兩條語句只能所有執行完成才能算此次事務執行完成數據庫
原子性是指事務是一個不可分割的工做單位,事務中的操做要麼所有成功,要麼所有失敗。好比在同一個事務中的SQL語句,要麼所有執行成功,要麼所有執行失敗
安全
官網上事務一致性的概念是:事務必須使數據庫從一個一致性狀態變換到另一個一致性狀態。以轉帳爲例子,A向B轉帳,假設轉帳以前這兩個用戶的錢加起來總共是2000,那麼A向B轉帳以後,無論這兩個帳戶怎麼轉,A用戶的錢和B用戶的錢加起來的總額仍是2000,這個就是事務的一致性。
bash
事務的隔離性是多個用戶併發訪問數據庫時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做數據所幹擾,多個併發事務之間要相互隔離。
併發
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即便數據庫發生故障也不該該對其有任何影響
事務的四大特性中最麻煩的是隔離性,也是咱們事務控制的主要內容 性能
默認狀況下,MySql事務是自動提交(Autocommit)的,若果須要明確的Commit和Rollback來提交和回滾事務,那麼就須要明確的事務控制命令來開始事務。
SET AUTOCOMMIT能夠修改當前鏈接的提交方式,若是設置了set autocommit=0,則設置以後的全部事務都須要經過明確的命令進行提交或者回滾。若是隻是對某些語句須要進行事務控制,則使用start transaction語句開始一個事務比較方便,這樣事務結束以後能夠自動回到自動提交的方式,若是但願全部的事務都不是自動提交的,那麼經過修改AUTOCOMMIT來控制事務比較方便,這樣不用在每一個事務開始的時候再執行start transaction.測試
//建立帳戶表
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');
複製代碼
使用"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)
複製代碼
下面咱們在數據庫模擬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。
經過手動回滾事務,使前面執行的操做無效
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)
複製代碼
多個線程開啓各自事務操做數據庫中數據時,數據庫系統要負責隔離操做,以保證各個線程在獲取數據時的準確性。
若是事務不考慮隔離性,可能會引起以下問題:
髒讀指一個事務讀取了另一個事務未提交的數據。
這是很是危險的,假設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元了,形成虛讀一樣會使銀行不知所措,到底以哪一個爲準。
不可重複讀的和幻讀很容易混淆,不可重複讀側重於修改,幻讀側重於新增或刪除。解決不可重複讀的問題只需鎖住知足條件的行,解決幻讀須要鎖表
MySQL數據庫共定義了四種隔離級別:
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)
複製代碼
同時打開兩個窗口模擬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以能應付大多數併發狀況,二是使用串行化會加大系統開銷,執行效率大大下降