🌇個人主頁:平凡的小蘇
📚學習格言:命運給你一個低的起點,是想看你精彩的翻盤,而不是讓你自甘墮落,腳下的路雖然難走,但我還能走,比起向陽而生,我更想嘗試逆風翻盤。
🛸Mysql專欄:Mysql內功修煉基地
> 家人們更新不易,你們的👍點贊👍和?關注?真的對我真重要,各位路 過的友友麻煩多多點贊關注。 歡迎你們的私信提問,感謝你們的轉發! 關注我,關注我,關注我,你們將會看到更多的優質內容!!
一、事務的概念
1、事務的四種特性
事務就是一組DML語句組成,這些語句在邏輯上存在相關性,這一組DML語句要么全部成功,要么全部失敗,是一個整體。例如銀行轉賬操作,上層看來是一個單純的轉賬操作,但是下層卻需要一條或多條SQL語句來完成轉賬操作,這一組SQL是一個整體,被稱為事務。事務還規定不同的客戶端看到的數據是不相同的。
一個 MySQL 數據庫,可不止你一個事務在運行,同一時刻,有大量的請求被包裝成事務,向 MySQL 服務器發起事務處理請求。如果多名用戶都訪問同樣的表數據,在不加保護的情況,絕對會出現問題。并且事務由多條 SQL 構成,也會存在執行到一半出錯或者不想再執行的情況,那么已經執行的怎么辦呢?
所以一個完整的事務,絕對不是簡單的 sql 集合,還需要滿足如下四個屬性:
原子性:一個事務(transaction)中的所有操作,要么全部完成,要么全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
一致性:在事務開始之前和事務結束以后,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及后續數據庫可以自發性地完成預定的工作。 原子性、隔離性、持久性是因,一致性是果。
隔離性:數據庫允許多個并發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務并發執行時由于交叉執行而導致數據的不一致。事務隔離分為不同級別,包括讀未提交( Read uncommitted )、讀提交( read committed )、可重復讀( repeatable read )和串行化( Serializable )
持久性:事務處理結束后,對數據的修改就是永久的,即便系統故障也不會丟失。
上面四個屬性,可以簡稱為 ACID 。
原子性(Atomicity,或稱不可分割性)
一致性(Consistency)
隔離性(Isolation,又稱獨立性)
持久性(Durability)。
2、事務的作用
當程序員在編寫上層代碼邏輯訪問數據庫時,事務能夠簡化編程時需要考慮的多種細節問題,例如我們在使用事務時,要么提交要么回滾,不用去擔心網絡異常、服務器宕機等問題。
3、存儲引擎對事務的支持
在 MySQL 中只有Innodb支持事務,而MyISAM不支持。
4、事務的提交方式
自動提交:
–查看MySQL事務的提交方式發現是自動提交
```mysql
##查看MySQL事務的提交方式發現是自動提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.02 sec)
**使用set改變MySQL的事務提交方式**:```sql
##將MySQL的事務提交方式修改為禁止自動提交
mysql> set autocommit=0;
Query OK, 0 rows affected (0.01 sec)mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)##將MySQL的事務提交方式修改為開啟自動提交
mysql> set autocommit=1;
Query OK, 0 rows affected (0.00 sec)mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
二、事務的啟動、回滾與提交
1、準備工作:調整MySQL的默認隔離級別為最低/創建測試表
為了看到演示現象,先將MySQL的默認隔離級別設置成讀未提交(最低)后退出重啟:
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
##修改默認隔離級別生效
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=UTF8;
2、證明事務的開始與回滾
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, '張三', 1030.00); -- 插入一條記錄
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, '李四', 13000.00); -- 在插入一條記錄
Query OK, 1 row affected (0.00 sec)
mysql> select * from account; -- 兩條記錄都在了
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 1030.00 |
| 2 | 李四 | 13000.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)
3、證明未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.30); -- 插入記錄
Query OK, 1 row affected (0.00 sec)
mysql> select * from account; -- 數據已經存在,但沒有commit,此時同時查看
-- 終端B
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.30 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> Aborted -- ctrl + \ 異常終止MySQL
-- 終端B
mysql> select * from account; -- 終端A崩潰前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 張三 | 100.30 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> select * from account; -- 數據自動回滾
Empty set (0.00 sec)
結論:
1、autocommit并不會影響begin后啟動的事務的提交,使用begin后必須輸入commit才能使數據持久化。
2、單句SQL本質就是事務,它的持久化和autocommit有關,autocommit為ON,表示單SQL無需手動commit自動提交,否則需要手動commit令數據持久化。(select有特殊情況,因為MySQL 有 MVCC )
3、事務可以手動回滾,同時,當操作異常,MySQL會自動回滾
4、我們能看到事務本身的原子性(rollback),持久性(commit)
三、事務的隔離級別
1、隔離性
1、MySQL服務可能會同時被多個客戶端進程(線程)訪問,訪問的方式以事務方式進行。
2、一個事務可能由多條SQL構成,也就意味著,任何一個事務,都有執行前,執行中,執行后的階段。而所謂的原子性,其實就是讓用戶層,要么看到執行前,要么看到執行后。執行中出現問題,可以隨時回滾。所以單個事務,對用戶表現出來的特性,就是原子性。
3、但是所有事務都要有個執行過程,那么在多個事務各自執行多個SQL的時候,就還是有可能會出現互相影響的情況。比如:多個事務同時訪問同一張表,甚至同一行數據。
4、數據庫中,為了保證事務執行過程中盡量不受干擾,就有了一個重要特征:隔離性 。
5、數據庫中,允許事務受不同程度的干擾,就有了一種重要特征:隔離級別
2、隔離級別
讀未提交【Read Uncommitted】: 在該隔離級別,所有的事務都可以看到其他事務沒有提交的執行結果。(實際生產中不可能使用這種隔離級別的),但是相當于沒有任何隔離性,也會有很多并發問題,如臟讀,幻讀,不可重復讀等,我們上面為了做實驗方便,用的就是這個隔離性。
讀提交【Read Committed】:該隔離級別是大多數數據庫的默認的隔離級別(不是 MySQL 默認的)。它滿足了隔離的簡單定義:一個事務只能看到其他的已經提交的事務所做的改變。這種隔離級別會引起不可重復讀,即一個事務執行時,如果多次 select,可能得到不同的結果。
可重復讀【Repeatable Read】: 這是 MySQL 默認的隔離級別,它確保同一個事務,在執行中,多次讀取操作數據時,會看到同樣的數據行。但是有的數據庫會有幻讀問題。(MySQL不會)
串行化【Serializable】: 這是事務的最高隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決了幻讀的問題。它在每個讀的數據行上面加上共享鎖。但是可能會導致超時和鎖競爭(這種隔離級別太極端,實際生產基本不使用)
隔離級別如何實現:隔離,基本都是通過鎖實現的,不同的隔離級別,鎖的使用是不同的。常見有,表鎖,行鎖,讀鎖,寫鎖,間隙鎖(GAP),Next-Key鎖(GAP+行鎖)等。
3、隔離級別的查看與設置隔離性
3.1查看隔離性
-- 查看
mysql> SELECT @@global.tx_isolation; --查看全局隔級別
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --查看會話(當前)全局隔級別
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --默認同上
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
3.2、設置隔離性
語法:
-- 設置當前會話 or 全局隔離級別語法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ
COMMITTED | REPEATABLE READ | SERIALIZABLE}
設置會話的隔離級別為串行化:
--設置當前會話隔離性,另起一個會話,看不多,只影響當前會話
mysql> set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation; --全局隔離性還是RR
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --會話隔離性成為串行化
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --同上
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
–設置全局隔離性,另起一個會話,會被影響
mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
-- 注意,如果沒有現象,關閉mysql客戶端,重新連接。
當一個會話修改了全局隔離級別,將會同時修改其他所有會話的全局隔離級別。
后續登錄時,會話和默認的隔離級別將會引用全局隔離級別的設置。
盡量保證隔離級別一致,閑的沒事不要改事務的隔離級別。
4、四種隔離級別詳解
4.1、讀未提交【Read Uncommitted】
--先設置當前的全局隔離級別為讀未提交,再退出MySQL重新登錄一下讓其生效
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.01 sec)
本文第二章就是用讀未提交來舉例的。
在多個并行的會話中啟動事務,一個事務在改動數據庫哪怕沒有commit提交,其他事務也是能夠實時的看到它修改的數據。一個事務在執行中,讀到另一個執行中事務的更新(或其他操作)但是未commit的數據,這種不合理的現象叫做臟讀(dirty read)
讀未提交的其他不合理現象還有不可重復讀、幻讀
讀未提交幾乎沒有加鎖,雖然效率高,但是問題太多,嚴重不建議采用。
4.2、讀提交【Read Committed】
-- 終端A
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
-- 重啟客戶端
mysql> select * from account; -- 查看當前數據
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 123.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; -- 手動開啟事務,同步的開始終端B事務
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=321.0 where id=1; -- 更新張三數據
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--切換終端到終端B,查看數據。
mysql> commit; --commit提交!
Query OK, 0 rows affected (0.01 sec)
-- 切換終端到終端B,再次查看數據。
-- 終端B
mysql> begin; -- 手動開啟事務,和終端A一前一后
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; -- 終端A commit之前,查看不到
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 123.00 | -- 老的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
-- 終端A commit之后,看到了!
-- but,此時還在當前事務中,并未commit,那么就造成了,同一個事務內,同樣的讀取,在不同的時間段
##(依舊還在事務操作中!),讀取到了不同的值,這種現象叫做不可重復讀(non reapeatable read)!!
##(這個是問題嗎??)
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 321.00 | -- 新的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
通過試驗可以發現讀提交級別,事務A在commit提交事務之前,所做的修改是不會被其他事務看到的,一旦事務A發起commit之后,其他事務就能看到事務A對數據的修改。這就造成了其他事務在不同的時間點select查看數據庫時,會查到不同的數據。這種現象叫不可重復讀。(事務中的讀取不是原子的)
4.3、可重復讀【Repeatable Read】
進行插入操作:
-- 終端A
mysql> set global transaction isolation level repeatable read; -- 設置全局隔離級別
RR
Query OK, 0 rows affected (0.01 sec)
-- 關閉終端重啟
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ | -- 隔離級別RR
+-----------------+
1 row in set, 1 warning (0.00 sec)
-- 終端A
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; -- 開啟事務,終端B同步開啟
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account (id,name,blance) values(3, '王五', 5432.0);
Query OK, 1 row affected (0.00 sec)
-- 切換到終端B,查看另一個事務是否能看到
mysql> commit; -- 提交事務
Query OK, 0 rows affected (0.00 sec)
-- 切換終端到終端B,查看數據。
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
-- 終端B
mysql> begin; -- 開啟事務
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; -- 終端A commit前 查看
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; -- 終端A commit后 查看
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> commit; -- 結束事務
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; -- 看到更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
多次查看,發現終端A在對應事務中insert的數據,在終端B的事務周期中,也沒有什么影響,也符合可重復的特點。但是,一般的數據庫在可重復讀情況的時候,無法屏蔽其他事務insert的數據(為什么?因為隔離性實現是對數據加鎖完成的,而insert待插入的數據因為并不存在,那么一般加鎖無法屏蔽這類問題),會造成雖然大部分內容是可重復讀的,但是insert的數據在可重復讀情況被讀取出來,導致多次查找時,會多查找出來新的記錄,就如同產生了幻覺。這種現象,叫做幻讀(phantom read)。很明顯,MySQL在RR級別的時候,是解決了幻讀問題的(解決的方式是用Next-Key鎖(GAP+行鎖)解決的。這塊比較難,有興趣同學了解一下)。
4.4、串行化
-- 對所有操作全部加鎖,進行串行化,不會有問題,但是只要串行化,效率很低,幾乎完全不會被采用
-- 終端A
mysql> set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin; -- 開啟事務,終端B同步開啟
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; -- 兩個讀取不會串行化,共享鎖
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
mysql> update account set blance=1.00 where id=1; -- 終端A中有更新或者其他操作,會阻
##塞。直到終端B事務提交。
Query OK, 1 row affected (18.19 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- 終端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; -- 兩個讀取不會串行化
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 張三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
mysql> commit; -- 提交之后,終端A中的update才會提交。
Query OK, 0 rows affected (0.00 sec)
結論:
其中隔離級別越嚴格,安全性越高,但數據庫的并發性能也就越低,往往需要在兩者之間找一個平
衡點。
不可重復讀的重點是修改和刪除:同樣的條件, 你讀取過的數據,再次讀取出來發現值不一樣了
幻讀的重點在于新增:同樣的條件, 第1次和第2次讀出來的記錄數不一樣
說明: mysql 默認的隔離級別是可重復讀,一般情況下不要修改
上面的例子可以看出,事務也有長短事務這樣的概念。事務間互相影響,指的是事務在并行執行的
時候,即都沒有commit的時候,影響會比較大