文章目錄
● 事務是需要在同一個處理單元中執行的一系列更新處理的集合。通過使用
事務,可以對數據庫中的數據更新處理的提交和取消進行管理。
● 事務處理的終止指令包括 COMMIT (提交處理)和 ROLLBACK (取消處
理)兩種。
● DBMS的事務具有原子性(Atomicity)、一致性(Consistency)、隔離性
(Isolation)和持久性(Durability)四種特性。通常將這四種特性的首字母
結合起來,統稱爲ACID特性。
什麼是事務
簡單來講,事務就是 需要在同一個處理單元中執行的一系列更新處理的集合 。對錶進行更新需要使用 INSERT 、 DELETE 或者UPDATE 三種語句。但通常情況下,更新處理並不是執行一次就結束了,而是需要執行一系列連續的操作。這時,事務就能體現出它的價值了。
說到事務的例子,請大家思考一下下述情況。現在,請大家把自己想象爲管理 Product (商品)表的程序員或者軟件工程師。銷售部門的領導對你提出瞭如下要求。“某某,經會議討論,我們決定把運動 T 恤的銷售單價下調 1000日元,同時把 T 恤衫的銷售單價上浮 1000日元,麻煩你去更新一下數據庫。”
此時的事務由如下兩條更新處理所組成。
1:將運動T恤的銷售單價降低1000日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = ' 運動 T 恤 ';
2:將T恤衫的銷售單價上浮1000日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T 恤衫 ';
上述①和②的操作一定要作爲同一個處理單元執行。如果只執行了①的操作而忘記了執行②的操作,或者反過來只執行了②的操作而忘記了執行①的操作,一定會受到領導的嚴厲批評。遇到這種需要在同一個處理單元中執行一系列更新操作的情況,一定要使用事務來進行處理。
創建事務
事務的語法
事務開始語句 ;
DML 語句① ;
DML 語句② ;
DML 語句③ ;
.
.
.
事務結束語句( COMMIT 或者 ROLLBACK )
使用事務開始語句和事務結束語句,將DML括起來就實現了一個事務的處理。實際上,在標準 SQL 中並沒有定義事務的開始語句,而是由各個 DBMS 自己來定義的。反之,事務的結束需要用戶明確地給出指示。
MySQL中的事務語法
START TRANSACTION;
-- 將運動 T 恤的銷售單價降低 1000 日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = ' 運動 T 恤 ';
-- 將 T 恤衫的銷售單價上浮 1000 日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T 恤衫 ';
COMMIT;
事務開啓
大部分情況下,事務在數據庫連接建立時就已經悄悄開始了,並不需要用戶再明確發出開始指令。
像這樣不使用指令而悄悄開始事務的情況下,應該如何區分各個事務呢?通常會有如下兩種情況。
A: 每條SQL語句就是一個事務(自動提交模式)
B: 直到用戶執行 COMMIT 或者 ROLLBACK 爲止算作一個事務
通常DBMS都是選擇其中一種模式,MySQL默認使用自動提交模式
該模式下的 DML 語句如下所示,每一條語句都括在事務的開始語句和結束語句之中。
START TRANSACTION;
-- 將運動 T 恤的銷售單價降低 1000 日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = ' 運動 T 恤 ';
COMMIT;
START TRANSACTION;
-- 將 T 恤衫的銷售單價上浮 1000 日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T 恤衫 ';
COMMIT;
COMMIT——事務提交
COMMIT 是提交事務包含的全部更新處理的結束指令,相當於文件處理中的覆蓋保存。一旦提交,就無法恢復到事務開始前的狀態了。
ROLLBACK——事務回滾
ROLLBACK 是取消事務包含的全部更新處理的結束指令,相當於文件處理中的放棄保存。一旦回滾,數據庫就會恢復到事務開始之前的狀態。
START TRANSACTION;
-- 將運動 T 恤的銷售單價降低 1000 日元
UPDATE Product
SET sale_price = sale_price - 1000
WHERE product_name = ' 運動 T 恤 ';
-- 將 T 恤衫的銷售單價上浮 1000 日元
UPDATE Product
SET sale_price = sale_price + 1000
WHERE product_name = 'T 恤衫 ';
ROLLBACK;
上述代碼中使用ROLLBACK,所以數據庫中數據不會發生改變。
ACID特性
DBMS 的事務都遵循四種特性,將這四種特性的首字母結合起來統稱爲 ACID 特性。這是所有 DBMS 都必須遵守的規則。
原子性(Atomicity)
原子性是指在事務結束時,其中所包含的更新處理要麼全部執行,要麼完全不執行,也就是要麼佔有一切要麼一無所有。
從事務中途停止的角度去考慮,就能比較容易理解原子性的重要性了。由於用戶在一個事務中定義了兩條 UPDATE 語句,DBMS 肯定不會只執行其中一條,否則就會對業務處理造成影響。
一致性(Consistency)
一致性指的是事務中包含的處理要滿足數據庫提前設置的約束,如主鍵約束或者 NOT NULL 約束等。 例如,設置了 NOT NULL 約束的列是不能更新爲 NULL 的,試圖插入違反主鍵約束的記錄就會出錯,無法執行。
對事務來說,這些不合法的 SQL 會被回滾。也就是說,這些 SQL 處理會被取消,不會執行。一致性也稱爲完整性。
隔離性(Isolation)
隔離性指的是保證不同事務之間互不干擾的特性。
該特性保證了事務之間不會互相嵌套。此外,在某個事務中進行的更改,在該事務結束之前,對其他事務而言是不可見的。
持久性(Durability)
持久性也可以稱爲耐久性,指的是在事務(不論是提交還是回滾)結束後,DBMS 能夠保證該時間點的數據狀態會被保存的特性。 即使由於系統故障導致數據丟失,數據庫也一定能通過某種手段進行恢復。
保證持久性的方法根據實現的不同而不同,其中最常見的就是將事務的執行記錄保存到硬盤等存儲介質中(該執行記錄稱爲日誌)。 當發生故障時,可以通過日誌恢復到故障發生前的狀態。
隱式事務
事務自動開啓、提交或回滾,比如insert、update、delete語句,事務的開啓、提交或回滾由mysql內部自動控制的。
查看變量 autocommit 是否開啓了自動提交:autocommit 爲ON表示開啓了自動提交。
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
顯式事務
事務需要手動開啓、提交或回滾,由開發者自己控制。
有2種方式手動控制事務:
方式1
//設置不自動提交事務
set autocommit=0;
//執行事務操作
commit|rollback;
方式2
start transaction;//開啓事務
//執行事務操作
commit|rollback;
只讀事務
表示在事務中執行的是一些只讀操作,如查詢,但是不會做insert、update、delete操作,數據庫內部對只讀事務可能會有一些性能上的優化。
用法如下:
start transaction read only;
示例:
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction read only;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
mysql> delete from test1;
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
只讀事務中執行delete會報錯。
savepoint關鍵字
在事務中我們執行了一大批操作,可能我們只想回滾部分數據,怎麼做呢?我們可以將一大批操作分爲幾個部分,然後指定回滾某個部分。可以使用 savepoin 來實現,效果如下:
下面演示savepoint效果
mysql> select * from test1;
Empty set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test1 values (1);
Query OK, 1 row affected (0.00 sec)
mysql> savepoint part1;//設置一個保存點
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test1 values (2);
Query OK, 1 row affected (0.00 sec)
mysql> rollback to part1;//將savepint = part1的語句到當前語句之間所有的操作回滾
Query OK, 0 rows affected (0.00 sec)
mysql> commit;//提交事務
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
從上面可以看出,執行了2次插入操作,最後只插入了1條數據。savepoint 需要結合 rollback to sp1 一起使用,可以將保存點 sp1 到 rollback to 之間的操作回滾掉。
事務隔離性級別
當多個事務同時進行的時候,如何確保當前事務中數據的正確性,比如A、B兩個事務同時進行的時候,A是否可以看到B已提交的數據或者B未提交的數據,這個需要依靠事務的隔離級別來保證,不同的隔離級別中所產生的效果是不一樣的。事務隔離級別主要是解決了上面多個事務之間數據可見性及數據正確性的問題。
事務隔離性級別分爲4種
1:讀未提交:READ-UNCOMMITTED
2:讀已提交:READ-COMMITTED
3:可重複讀:REPEATABLE-READ
4:串行:SERIALIZABLE
上面4中隔離級別越來越強,會導致數據庫的併發性也越來越低。
查看隔離性級別
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
上面使用show variables like ‘transaction_isolation’;查看隔離性級別。
隔離性級別設置
1:修改mysql中的my.init文件,我們將隔離級別設置爲:READ-UNCOMMITTED,如下:
# 隔離級別設置,READ-UNCOMMITTED讀未提交,READ-COMMITTED讀已提交,REPEATABLE-READ可重複讀,SERIALIZABLE串行
transaction-isolation=READ-UNCOMMITTED
2:以管理員身份打開cmd窗口,重啓mysql,如下:
C:\Windows\system32>net stop mysql
mysql 服務正在停止..
mysql 服務已成功停止。
C:\Windows\system32>net start mysql
mysql 服務正在啓動 .
mysql 服務已經啓動成功。
各種隔離性級別會出現的問題
隔離級別 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|
READ-UNCOMMITTED | 有 | 有 | 無 |
READ-COMMITTED | 無 | 有 | 無 |
REPEATABLE-READ | 無 | 無 | 有 |
SERIALIZABLE | 無 | 無 | 無 |
髒讀:一個事務在執行的過程中讀取到了其他事務還沒有提交的數據,髒讀其實就是讀取未提交。
讀已提交:事務中的每次讀取操作,讀取到的都是數據庫中其他事務已提交的最新的數據。
可重複讀:一個事務操作中對於一個讀取操作不管多少次,讀取到的結果都是一樣的。
幻讀:幻讀在可重複讀的模式下才會出現,其他隔離級別中不會出現。
幻讀演示
幻讀只會在 REPEATABLE-READ (可重複讀)級別下出現,需要先把隔離級別改爲可重複讀。將隔離級別置爲 REPEATABLE-READ
# 隔離級別設置,READ-UNCOMMITTED讀未提交,READ-COMMITTED讀已提交,REPEATABLE-READ可重複讀,SERIALIZABLE串行
transaction-isolation=REPEATABLE-READ
重啓mysql:
C:\Windows\system32>net stop mysql
mysql 服務正在停止..
mysql 服務已成功停止。
C:\Windows\system32>net start mysql
mysql 服務正在啓動 .
mysql 服務已經啓動成功。
查看隔離性級別:
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
準備數據:
mysql> create table t_user(id int primary key,name varchar(16) unique key);
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t_user;
Empty set (0.00 sec)
按時間順序在2個窗口中執行下面操作:
時間 | 窗口A | 窗口B |
---|---|---|
T1 | start transaction; | |
T2 | start transaction; | |
T3 | insert into t_user values (1,‘Lw中’); | |
T4 | select * from t_user; | |
T5 | select * from t_user where name=‘Lw中’; | |
T6 | commit; | |
T7 | insert into t_user values (2,‘Lw中’); | |
T8 | select * from t_user where name=‘Lw中’; | |
T9 | commit; |
A窗口如下:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user where name='Lw中';
Empty set (0.00 sec)
mysql> insert into t_user values (2,'Lw中');
ERROR 1062 (23000): Duplicate entry 'Lw中' for key 'name'
mysql> select * from t_user where name='Lw中';
Empty set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
B窗口如下:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t_user values (1,'Lw中');
Query OK, 1 row affected (0.00 sec)
mysql> select * from t_user;
+----+---------------+
| id | name |
+----+---------------+
| 1 | Lw中 |
+----+---------------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
A想插入數據Lw中 ,插入之前先查詢了一下(T5時刻)該用戶是否存在,發現不存在,然後在T7時刻執行插入,報錯了,報數據已經存在了,因爲T6時刻 B 已經插入了Lw中 。
然後A有點鬱悶,剛纔查的時候不存在的,然後A不相信自己的眼睛,又去查一次(T8時刻),發現Lw中還是不存在的。
此時A心裏想:數據明明不存在啊,爲什麼無法插入呢?這不是懵逼了麼,A覺得如同發生了幻覺一樣。
讀未提交:READ-UNCOMMITTED
讀未提交情況下,可以讀取到其他事務還未提交的數據,多次讀取結果不一樣,出現了髒讀、不可重複讀
讀已提交:READ-COMMITTED
讀已提交情況下,無法讀取到其他事務還未提交的數據,可以讀取到其他事務已經提交的數據,多次讀取結果不一樣,未出現髒讀,出現了讀已提交、不可重複讀。
可重複讀:REPEATABLE-READ
可重複讀情況下,未出現髒讀,未讀取到其他事務已提交的數據,多次讀取結果一致,即可重複讀。
串行(hang):SERIALIZABLE
SERIALIZABLE會讓併發的事務串行執行(多個事務之間讀寫、寫讀、寫寫會產生互斥,效果就是串行執行,多個事務之間的讀讀不會產生互斥)。
讀寫互斥:事務A中先讀取操作,事務B發起寫入操作,事務A中的讀取會導致事務B中的寫入處於等待狀態,直到A事務完成爲止。
表示我開啓一個事務,爲了保證事務中不會出現上面說的問題(髒讀、不可重複讀、讀已提交、幻讀),那麼我讀取的時候,其他事務有修改數據的操作需要排隊等待,等待我讀取完成之後,他們纔可以繼續。
寫讀、寫寫也是互斥的,讀寫互斥類似。
串行演示
將隔離級別置爲 SERIALIZABLE
# 隔離級別設置,READ-UNCOMMITTED讀未提交,READ-COMMITTED讀已提交,REPEATABLE-READ可重複讀,SERIALIZABLE串行
transaction-isolation=SERIALIZABLE
重啓mysql:
C:\Windows\system32>net stop mysql
mysql 服務正在停止..
mysql 服務已成功停止。
C:\Windows\system32>net start mysql
mysql 服務正在啓動 .
mysql 服務已經啓動成功。
查看隔離性級別:
mysql> show variables like 'transaction_isolation';
+-----------------------+--------------+
| Variable_name | Value |
+-----------------------+--------------+
| transaction_isolation | SERIALIZABLE |
+-----------------------+--------------+
1 row in set, 1 warning (0.00 sec)
先清空test1表數據:
delete from test1;
select * from test1;
按時間順序在2個窗口中執行下面操作:
時間 | 窗口A | 窗口B |
---|---|---|
T1 | start transaction; | |
T2 | select * from test1; | |
T3 | start transaction; | |
T4 | insert into test1 values (1); | |
T5 | commit; | |
T6 | commit; |
按時間順序運行上面的命令,會發現T4-B這樣會被阻塞,直到T5-A執行完畢。
上面這個演示的是讀寫互斥產生的效果,大家可以自己去寫一下寫讀、寫寫互斥的效果。
可以看出來,事務只能串行執行了。串行情況下不存在髒讀、不可重複讀、幻讀的問題了。
關於隔離性級別的選擇
1: 需要對各種隔離級別產生的現象非常瞭解,然後選擇的時候才能遊刃有餘
2:隔離級別越高,併發性也低,比如最高級別 SERIALIZABLE 會讓事物串行執行,併發操作變成串行了,會導致系統性能直接降低
3: 具體選擇哪種需要結合具體的業務來選擇
4: 讀已提交(READ-COMMITTED)通常用的比較多