Mysql系列第十五講
- 什么是事務?
- 事務的幾個特性(ACID)
- Mysql中事務操作
- savepoint關鍵字
- 只讀事務
- 事務中的一些問題
- 事務的隔離級別
- 關于隔離級別的選擇
什么是事務?
資料庫中的事務是指對資料庫執行一批操作,這些操作最終要么全部執行成功,要么全部失敗,不會存在部分成功的情況,
舉個例子
比如A用戶給B用戶轉賬100操作,程序如下:
1.從A賬戶扣100
2.給B賬戶加100
如果在事務的支持下,上面最終只有2種結果:
-
操作成功:A賬戶減少100;B賬戶增加100
-
操作失敗:A、B兩個賬戶都沒有發生變化
如果沒有事務的支持,可能出現錯:A賬戶減少了100,此時系統掛了,導致B賬戶沒有加上100,而A賬戶憑空少了100,
事務的幾個特性(ACID)
原子性(Atomicity)
事務的整個程序如原子操作一樣,最終要么全部成功,或者全部失敗,這個原子性是從最終結果來看的,從最終結果來看這個程序是不可分割的,
一致性(Consistency)
事務開始之前、執行中、執行完畢,這些時間點,多個人去觀察事務操作的資料的時候,看到的資料都是一致的,比如在事務操作程序中,A連接看到的是100,那么B此時也去看的時候也是100,不會說AB看到的資料不一樣,他們在某個時間點看到的資料是一致的,
隔離性(Isolation)
一個事務的執行不能被其他事務干擾,即一個事務內部的操作及使用的資料對并發的其他事務是隔離的,并發執行的各個事務之間不能互相干擾,
持久性(Durability)
一個事務一旦提交,他對資料庫中資料的改變就應該是永久性的,當事務提交之后,資料會持久化到硬碟,修改是永久性的,
Mysql中事務操作
mysql中事務默認是隱式事務,執行insert、update、delete操作的時候,資料庫自動開啟事務、提交或回滾事務,
是否開啟隱式事務是由變數autocommit控制的,
所以事務分為隱式事務和顯式事務,
隱式事務
事務自動開啟、提交或回滾,比如insert、update、delete陳述句,事務的開啟、提交或回滾由mysql內部自動控制的,
查看變數autocommit是否開啟了自動提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)
autocommit為ON表示開啟了自動提交,
顯式事務
事務需要手動開啟、提交或回滾,由開發者自己控制,
2種方式手動控制事務:
方式1:
語法:
//設定不自動提交事務
set autocommit=0;
//執行事務操作
commit|rollback;
示例1:提交事務操作,如下:
mysql> create table test1 (a int);
Query OK, 0 rows affected (0.01 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test1 values(1);
Query OK, 1 row 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:回滾事務操作,如下:
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test1 values(2);
Query OK, 1 row affected (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
可以看到上面資料回滾了,
我們把autocommit還原回去:
mysql> set autocommit=1;
Query OK, 0 rows affected (0.00 sec)
方式2:
語法:
start transaction;//開啟事務
//執行事務操作
commit|rollback;
示例1:提交事務操作,如下:
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into test1 values (2);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test1 values (3);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
上面成功插入了2條資料,
示例2:回滾事務操作,如下:
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from test1;
Query OK, 3 rows affected (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
上面事務中我們洗掉了test1的資料,顯示洗掉了3行,最后回滾了事務,
savepoint關鍵字
在事務中我們執行了一大批操作,可能我們只想回滾部分資料,怎么做呢?
我們可以將一大批操作分為幾個部分,然后指定回滾某個部分,可以使用savepoin來實作,效果如下:
先清除test1表資料:
mysql> delete from test1;
Query OK, 3 rows affected (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
演示savepoint效果,認真看:
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之間的操作回滾掉,
只讀事務
表示在事務中執行的是一些只讀操作,如查詢,但是不會做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會報錯,
事務中的一些問題
這些問題主要是基于資料在多個事務中的可見性來說的,
臟讀
一個事務在執行的程序中讀取到了其他事務還沒有提交的資料,
這個還是比較好理解的,
讀已提交
從字面上我們就可以理解,即一個事務操作程序中可以讀取到其他事務已經提交的資料,
事務中的每次讀取操作,讀取到的都是資料庫中其他事務已提交的最新的資料(相當于當前讀)
可重復讀
一個事務操作中對于一個讀取操作不管多少次,讀取到的結果都是一樣的,
幻讀
臟讀、不可重復讀、可重復讀、幻讀,其中最難理解的是幻讀
以mysql為例:
幻讀在可重復讀的模式下才會出現,其他隔離級別中不會出現
幻讀現象例子:
可重復讀模式下,比如有個用戶表,手機號碼為主鍵,有兩個事物進行如下操作
事務A操作如下:
1、打開事務
2、查詢號碼為X的記錄,不存在
3、插入號碼為X的資料,插入報錯(為什么會報錯,先向下看)
4、查詢號碼為X的記錄,發現還是不存在(由于是可重復讀,所以讀取記錄X還是不存在的)
事物B操作:在事務A第2步操作時插入了一條X的記錄,所以會導致A中第3步插入報錯(違反了唯一約束)
上面操作對A來說就像發生了幻覺一樣,明明查詢X(A中第二步、第四步)不存在,但卻無法插入成功
幻讀可以這么理解:事務中后面的操作(插入號碼X)需要上面的讀取操作(查詢號碼X的記錄)提供支持,但讀取操作卻不能支持下面的操作時產生的錯誤,就像發生了幻覺一樣,
如果還是理解不了的,繼續向下看,后面后詳細的演示,
事務的隔離級別
當多個事務同時進行的時候,如何確保當前事務中資料的正確性,比如A、B兩個事物同時進行的時候,A是否可以看到B已提交的資料或者B未提交的資料,這個需要依靠事務的隔離級別來保證,不同的隔離級別中所產生的效果是不一樣的,
事務隔離級別主要是解決了上面多個事務之間資料可見性及資料正確性的問題,
隔離級別分為4種:
讀未提交:READ-UNCOMMITTED
讀已提交:READ-COMMITTED
可重復讀:REPEATABLE-READ
串行:SERIALIZABLE
上面4中隔離級別越來越強,會導致資料庫的并發性也越來越低,
查看隔離級別
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set, 1 warning (0.00 sec)
隔離級別的設定
分2步驟,修改檔案、重啟mysql,如下:
修改mysql中的my.init檔案,我們將隔離級別設定為:READ-UNCOMMITTED,如下:
# 隔離級別設定,READ-UNCOMMITTED讀未提交,READ-COMMITTED讀已提交,REPEATABLE-READ可重復讀,SERIALIZABLE串行
transaction-isolation=READ-UNCOMMITTED
以管理員身份打開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 | 無 | 無 | 無 |
表格中和網上有些不一樣,主要是幻讀這塊,幻讀只會在可重復讀級別中才會出現,其他級別下不存在,
下面我們來演示一下,各種隔離級別中可見性的問題,開啟兩個視窗,叫做A、B視窗,兩個視窗中登錄mysql,
READ-UNCOMMITTED:讀未提交
將隔離級別置為READ-UNCOMMITTED:
# 隔離級別設定,READ-UNCOMMITTED讀未提交,READ-COMMITTED讀已提交,REPEATABLE-READ可重復讀,SERIALIZABLE串行
transaction-isolation=READ-UNCOMMITTED
重啟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 | READ-UNCOMMITTED |
+-----------------------+----------------+
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 | select * from test1; | |
| T6 | select * from test1; | |
| T7 | commit; | |
| T8 | commit; |
A視窗如下:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in 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 test1 values (1);
Query OK, 1 row affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
看一下:
T2-A:無資料,T6-A:有資料,T6時刻B還未提交,此時A已經看到了B插入的資料,說明出現了臟讀,
T2-A:無資料,T6-A:有資料,查詢到的結果不一樣,說明不可重復讀,
結論:讀未提交情況下,可以讀取到其他事務還未提交的資料,多次讀取結果不一樣,出現了臟讀、不可重復讀
READ-COMMITTED:讀已提交
將隔離級別置為READ-COMMITTED
# 隔離級別設定,READ-UNCOMMITTED讀未提交,READ-COMMITTED讀已提交,REPEATABLE-READ可重復讀,SERIALIZABLE串行
transaction-isolation=READ-COMMITTED
重啟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 | READ-COMMITTED |
+-----------------------+----------------+
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 | select * from test1; | |
| T6 | select * from test1; | |
| T7 | commit; | |
| T8 | select * from test1; | |
| T9 | commit; |
A視窗如下:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in 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 test1 values (1);
Query OK, 1 row affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
看一下:
T5-B:有資料,T6-A視窗:無資料,A看不到B的資料,說明沒有臟讀,
T6-A視窗:無資料,T8-A:看到了B插入的資料,此時B已經提交了,A看到了B已提交的資料,說明可以讀取到已提交的資料,
T2-A、T6-A:無資料,T8-A:有資料,多次讀取結果不一樣,說明不可重復讀,
結論:讀已提交情況下,無法讀取到其他事務還未提交的資料,可以讀取到其他事務已經提交的資料,多次讀取結果不一樣,未出現臟讀,出現了讀已提交、不可重復讀,
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)
先清空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 | select * from test1; | |
| T6 | select * from test1; | |
| T7 | commit; | |
| T8 | select * from test1; | |
| T9 | commit; | |
| T10 | select * from test1; |
A視窗如下:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> select * from test1;
Empty set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from test1;
+------+
| a |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
B視窗如下:
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> select * from test1;
+------+
| a |
+------+
| 1 |
| 1 |
+------+
2 rows in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
看一下:
T2-A、T6-A視窗:無資料,T5-B:有資料,A看不到B的資料,說明沒有臟讀,
T8-A:無資料,此時B已經提交了,A看不到B已提交的資料,A中3次讀的結果一樣都是沒有資料的,說明可重復讀,
結論:可重復讀情況下,未出現臟讀,未讀取到其他事務已提交的資料,多次讀取結果一致,即可重復讀,
幻讀演示
幻讀只會在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> insert into t_user values (1,'路人甲Java'),(2,'路人甲Java');
ERROR 1062 (23000): Duplicate entry '路人甲Java' for key 'name'
mysql> select * from t_user;
Empty set (0.00 sec)
上面我們創建t_user表,name添加了唯一約束,表示name不能重復,否則報錯,
按時間順序在2個視窗中執行下面操作:
| 時間 | 視窗A | 視窗B |
|---|---|---|
| T1 | start transaction; | |
| T2 | start transaction; | |
| T3 | – 插入路人甲Java | insert into t_user values (1,‘路人甲Java’); |
| T4 | select * from t_user; | |
| T5 | – 查看路人甲Java是否存在 | select * from t_user where name=‘路人甲Java’; |
| T6 | commit; | |
| T7 | – 插入路人甲Java | insert into t_user values (2,‘路人甲Java’); |
| T8 | – 查看路人甲Java是否存在 | select * from t_user where name=‘路人甲Java’; |
| T9 | commit; |
A視窗如下:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user where name='路人甲Java';
Empty set (0.00 sec)
mysql> insert into t_user values (2,'路人甲Java');
ERROR 1062 (23000): Duplicate entry '路人甲Java' for key 'name'
mysql> select * from t_user where name='路人甲Java';
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,'路人甲Java');
Query OK, 1 row affected (0.00 sec)
mysql> select * from t_user;
+----+---------------+
| id | name |
+----+---------------+
| 1 | 路人甲Java |
+----+---------------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
看一下:
A想插入資料路人甲Java,插入之前先查詢了一下(T5時刻)該用戶是否存在,發現不存在,然后在T7時刻執行插入,報錯了,報資料已經存在了,因為T6時刻B已經插入了路人甲Java,
然后A有點郁悶,剛才查的時候不存在的,然后A不相信自己的眼睛,又去查一次(T8時刻),發現路人甲Java還是不存在的,
此時A心里想:資料明明不存在啊,為什么無法插入呢?這不是懵逼了么,A覺得如同發生了幻覺一樣,
SERIALIZABLE:串行
SERIALIZABLE會讓并發的事務串行執行,
看效果:
將隔離級別置為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 | select * from test1; | |
| T6 | commit; | |
| T7 | commit; |
按時間順序運行上面的命令,會發現T4-B這樣會被阻塞,直到T6-A執行完畢,
可以看出來,事務只能串行執行了,串行情況下不存在臟讀、不可重復讀、幻讀的問題了,
關于隔離級別的選擇
-
需要對各種隔離級別產生的現象非常了解,然后選擇的時候才能游刃有余
-
隔離級別越高,并發性也低,比如最高級別SERIALIZABLE會讓事物串行執行,并發操作變成串行了,會導致系統性能直接降低,
-
具體選擇哪種需要結合具體的業務來選擇,
-
讀已提交(READ-COMMITTED)通常用的比較多,
Java
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/128490.html
標籤:AI
上一篇:pymongo多條件篩選時間報錯‘module‘ object is not callable
下一篇:Mysql事務隔離級別與鎖機制
