事務
一.CURD不加控制,會有什么問題?
二.什么是事務?
事務就是一組DML語句組成,這些語句在邏輯上存在相關性,這一組DML語句要么全部成功,要么全部失敗,是一個整體。MySQL提供一種機制,保證我們達到這樣的效果。事務還規定不同的客戶端看到的數據是不相同的。
事務就是要做的或所做的事情,主要用于處理操作量大,復雜度高的數據。假設一種場景:你畢業了,學校的教務系統后臺 MySQL 中,不在需要你的數據,要刪除你的所有信息(一般不會:) ), 那么要刪除你的基本信息(姓名,電話,籍貫等)的同時,也刪除和你有關的其他信息,比如:你的各科成績,你在校表現,甚至你在論壇發過的文章等。這樣,就需要多條 MySQL 語句構成,那么所有這些操作合起來,就構成了一個事務。
正如我們上面所說,一個 MySQL 數據庫,可不止你一個事務在運行,同一時刻,甚至有大量的請求被包裝成事務,在向 MySQL 服務器發起事務處理請求。而每條事務至少一條 SQL ,最多很多 SQL ,這樣如果大家都訪問同樣的表數據,在不加保護的情況,就絕對會出現問題。甚至,因為事務由多條 SQL 構成,那么,也會存在執行到一半出錯或者不想再執行的情況,那么已經執行的怎么辦呢?
所有,一個完整的事務,絕對不是簡單的 sql 集合,還需要滿足如下四個屬性:
-
原子性:一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
-
一致性:在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續數據庫可以自發性地完成預定的工作。做到原子性,隔離性,持久性即可以保證一致性. 一致性是果其他是因.
-
隔離性:數據庫允許多個并發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交( Read uncommitted )、讀提交( read committed )、可重復讀( repeatable read )和串行化( Serializable )
-
持久性:事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
上面四個屬性,可以簡稱為 ACID 。
原子性(Atomicity,或稱不可分割性)
一致性(Consistency)
隔離性(Isolation,又稱獨立性)
持久性(Durability)。
三.為什么會出現事務
事務被 MySQL 編寫者設計出來,**本質是為了當應用程序訪問數據庫的時候,事務能夠簡化我們的編程模型,不需要我們去考慮各種各樣的潛在錯誤和并發問題.**可以想一下當我們使用事務時,要么提交,要么回滾,我們不會去考慮網絡異常了,服務器宕機了,同時更改一個數據怎么辦對吧?因此事務本質上是為了應用層服務的.而不是伴隨著數據庫系統天生就有的.
備注:我們后面把 MySQL
中的一行信息,稱為一行記錄
四.事務的版本支持
在 MySQL 中只有使用了 Innodb
數據庫引擎的數據庫或表才支持事務, MyISAM
不支持。
查看數據庫引擎
五.事務提交方式
事務的提交方式常見的有兩種:
- 自動提交
- 手動提交
查看事務提交方式
用 SET
來改變 MySQL
的自動提交模式:
六.事務常見操作方式
簡單銀行用戶表
- 提前準備
## 為了便于演示,我們將mysql的默認隔離級別設置成讀未提交。
## 具體操作我們后面專門會講,現在已使用為主。
mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> quit
Bye
##需要重啟終端,進行查看
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
- 創建測試表
create table if not exists account(
id int primary key,
name varchar(50) not null default '',
blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8MB4;
- 正常演示 - 證明事務的開始與回滾
mysql> show variables like 'autocommit'; -- 查看事務是否自動提交。我們故意設置成自
動提交,看看該選項是否影響begin
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)mysql> start transaction; -- 開始一個事務begin也可以,推薦begin
Query OK, 0 rows affected (0.00 sec)mysql> savepoint save1; -- 創建一個保存點save1
Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '張三', 100); -- 插入一條記錄
Query OK, 1 row affected (0.05 sec)mysql> savepoint save2; -- 創建一個保存點save2
Query OK, 0 rows affected (0.01 sec)mysql> insert into account values (2, '李四', 10000); -- 在插入一條記錄
Query OK, 1 row affected (0.00 sec)mysql> select * from account; -- 兩條記錄都在了
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)mysql> rollback to save2; -- 回滾到保存點save2
Query OK, 0 rows affected (0.03 sec)mysql> select * from account; -- 一條記錄沒有了
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)mysql> rollback; -- 直接rollback,回滾在最開始
Query OK, 0 rows affected (0.00 sec)mysql> select * from account; -- 所有剛剛的記錄沒有了
Empty set (0.00 sec)
- 非正常演示1 - 證明未commit,客戶端崩潰,MySQL自動會回滾(隔離級別設置為讀未提交)
-- 終端A
mysql> select * from account; -- 當前表內無數據
Empty set (0.00 sec)mysql> show variables like 'autocommit'; -- 依舊自動提交
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)mysql> begin; --開啟事務
Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '張三', 100); -- 插入記錄
Query OK, 1 row affected (0.00 sec)mysql> select * from account; --數據已經存在,但沒有commit,此時同時查看終端B
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)mysql> Aborted -- ctrl + \ 異常終止MySQL--終端B
mysql> select * from account; --終端A崩潰前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)mysql> select * from account; --數據自動回滾
Empty set (0.00 sec)
- 非正常演示2 - 證明commit了,客戶端崩潰,MySQL數據不會在受影響,已經持久化
--終端 A
mysql> show variables like 'autocommit'; -- 依舊自動提交
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)mysql> select * from account; -- 當前表內無數據
Empty set (0.00 sec)mysql> begin; -- 開啟事務
Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (1, '張三', 100); -- 插入記錄
Query OK, 1 row affected (0.00 sec)mysql> commit; --提交事務
Query OK, 0 rows affected (0.04 sec)mysql> Aborted -- ctrl + \ 異常終止MySQL--終端 B
mysql> select * from account; --數據存在了,所以commit的作用是將數據持久化到MySQL中
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
- 非正常演示3 - 對比試驗。證明begin操作會自動更改提交方式,不會受MySQL是否自動提交影響,只要是手動開啟的事務,就必須要手動commit
-- 終端 A
mysql> select *from account; --查看歷史數據
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)mysql> show variables like 'autocommit'; --查看事務提交方式
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)mysql> set autocommit=0; --關閉自動提交
Query OK, 0 rows affected (0.00 sec)mysql> show variables like 'autocommit'; --查看關閉之后結果
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)mysql> begin; --開啟事務
Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (2, '李四', 10000); --插入記錄
Query OK, 1 row affected (0.00 sec)mysql> select *from account; --查看插入記錄,同時查看終端B
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)mysql> Aborted --再次異常終止-- 終端B
mysql> select * from account; --終端A崩潰前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)mysql> select * from account; --終端A崩潰后,自動回滾
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
- 非正常演示4 - 證明單條
SQL
與事務的關系
--實驗一
-- 終端A
mysql> select * from account;
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)mysql> set autocommit=0; --關閉自動提交
Query OK, 0 rows affected (0.00 sec)mysql> insert into account values (2, '李四', 10000); --插入記錄
Query OK, 1 row affected (0.00 sec)mysql> select *from account; --查看結果,已經插入。此時可以在查看終端B
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)mysql> ^DBye --ctrl + \ or ctrl + d,終止終端--終端B
mysql> select * from account; --終端A崩潰前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)mysql> select * from account; --終端A崩潰后
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)-- 實驗二
--終端A
mysql> show variables like 'autocommit'; --開啟默認提交,或者關閉默認提交但手動commit,效果相同
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)mysql> select * from account;
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)mysql> insert into account values (2, '李四', 10000);
Query OK, 1 row affected (0.01 sec)mysql> select *from account; --數據已經插入
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)mysql> Aborted --異常終止--終端B
mysql> select * from account; --終端A崩潰前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)mysql> select * from account; --終端A崩潰后,并不影響,已經持久化。autocommit
起作用
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.00 |
| 2 | 李四 |10000.00|
+----+--------+--------+
2 rows in set (0.00 sec)
結論:
- 只要輸入begin或者start transaction,事務便必須要通過commit提交,才會持久化,與是否設置set autocommit無關。
- 事務可以手動回滾,同時,當操作異常,MySQL會自動回滾
- 對于
InnoDB
每一條 SQL 語言都默認封裝成事務,自動提交。(select有特殊情況,因為MySQL
有MVCC
)- 從上面的例子,我們能看到事務本身的原子性(回滾),持久性(commit)
- 那么隔離性?一致性?
事務操作注意事項
- 如果沒有設置保存點,也可以回滾,只能回滾到事務的開始。直接使用 rollback(前提是事務還沒有提交)
- 如果一個事務被提交了(commit),則不可以回退(rollback)
- 可以選擇回退到哪個保存點
InnoDB
支持事務,MyISAM
不支持事務- 開始事務可以使
start transaction
或者begin
七.事務隔離級別
如何理解隔離性1
- MySQL服務可能會同時被多個客戶端進程(線程)訪問,訪問的方式以事務方式進行
- 一個事務可能由多條SQL構成,也就意味著,任何一個事務,都有執行前,執行中,執行后的階段。而所謂的原子性,其實就是讓用戶層,要么看到執行前,要么看到執行后。執行中出現問題,可以隨時回滾。所以單個事務,對用戶表現出來的特性,就是原子性。
- 但,畢竟所有事務都要有個執行過程,那么在多個事務各自執行多個SQL的時候,就還是有可能會出現互相影響的情況。比如:多個事務同時訪問同一張表,甚至同一行數據。
- 就如同你媽媽給你說:你要么別學,要學就學到最好。至于你怎么學,中間有什么困難,你媽媽不關心。那么你的學習,對你媽媽來講,就是原子的。那么你學習過程中,很容易受別人干擾,此時,就需要將你的學習隔離開,保證你的學習環境是健康的。
- 數據庫中,為了保證事務執行過程中盡量不受干擾,就有了一個重要特征:隔離性
- 數據庫中,允許事務受不同程度的干擾,就有了一種重要特征:隔離級別
(1).隔離級別
- 讀未提交【Read Uncommitted】: 在該隔離級別,所有的事務都可以看到其他事務沒有提交的執行結果。(實際生產中不可能使用這種隔離級別的),但是相當于沒有任何隔離性,也會有很多并發問題,如臟讀,幻讀,不可重復讀等,我們上面為了做實驗方便,用的就是這個隔離性。
- **讀提交【Read Committed】 :**該隔離級別是大多數數據庫的默認的隔離級別(不是 MySQL 默認的)。它滿足了隔離的簡單定義:一個事務只能看到其他的已經提交的事務所做的改變。這種隔離級別會引起不可重復讀,即一個事務執行時,如果多次 select, 可能得到不同的結果。
- 可重復讀【Repeatable Read】: 這是 MySQL 默認的隔離級別,它確保同一個事務,在執行中,多次讀取操作數據時,會看到同樣的數據行。但是會有幻讀問題。
- 串行化【Serializable】: 這是事務的最高隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決了幻讀的問題。它在每個讀的數據行上面加上共享鎖,。但是可能會導致超時和鎖競爭(這種隔離級別太極端,實際生產基本不使用)
隔離級別如何實現:隔離,基本都是通過鎖實現的,不同的隔離級別,鎖的使用是不同的。常見有,表鎖,行鎖,讀鎖,寫鎖,間隙鎖(GAP),Next-Key鎖(GAP+行鎖)等。不過,我們目前現有這個認識就行,先關注上層使用。
(2).查看與設置隔離性
①.讀未提交【Read Uncommitted】
②.讀提交【Read Committed】
③.可重復讀【Repeatable Read】
- rr或rc或ru級別下對于插入問題,在讀取時可能看到新插入的數據叫做幻讀.注意幻讀是針對insent 這個行為,mysql沒有幻讀問題.
⑤.串行化【serializable】
總結:
- 其中隔離級別越嚴格,安全性越高,但數據庫的并發性能也就越低,往往需要在兩者之間找一個平衡點。
- 不可重復讀的重點是修改和刪除:同樣的條件, 你讀取過的數據,再次讀取出來發現值不一樣了
- 幻讀的重點在于新增:同樣的條件, 第1次和第2次讀出來的記錄數不一樣
- 說明: mysql 默認的隔離級別是可重復讀,一般情況下不要修改
- 上面的例子可以看出,事務也有長短事務這樣的概念。事務間互相影響,指的是事務在并行執行的時候,即都沒有commit的時候,影響會比較大。
(3).一致性(Consistency)
- 事務執行的結果,必須使數據庫從一個一致性狀態,變到另一個一致性狀態。當數據庫只包含事務成功提交的結果時,數據庫處于一致性狀態。如果系統運行發生中斷,某個事務尚未完成而被迫中斷,而改未完成的事務對數據庫所做的修改已被寫入數據庫,此時數據庫就處于一種不正確(不一致)的狀態。因此一致性是通過原子性來保證的。
- 其實一致性和用戶的業務邏輯強相關,一般MySQL提供技術支持,但是一致性還是要用戶業務邏輯做支撐,也就是,一致性,是由用戶決定的。
- 而技術上,通過AID保證C
(4).推薦閱讀
https://www.jianshu.com/p/398d788e1083
https://tech.meituan.com/2014/08/20/innodb-lock.html
https://www.cnblogs.com/aspirant/p/9177978.html