mysql的事務性,鎖和隔離性
事務性:
所謂事務性,一句話概括:一個組操作的各個單元,執行情況要麼都成功,要麼都執行失敗。
開門見山:
事務的四大特性(ACID):
1.原子性(automicity):一個事物必須看做一個不可分割的最小工作單元,整個事務中的所有操作要麼都提交成功,要麼全部失敗回滾。對於事務而言,不可能只執行其中一部分,這就是事務的原子性。
2.一致性(consistency):數據庫總是從一個一致性的狀態轉換到另一種一致性的狀態。
3.隔離性(isolation):一個事務所做的修改在最終提交之前,對其他的事務是不可見的。
4.持久性(durability):一旦事務提交,其所做的一切修改必須永久保存在數據庫中,此時即使系統崩潰,修改的數據也不會丟失。
MySQL的是先將所有操作到日誌中記錄下來,再去真正操作。而這種機制就是靠事務日誌來實現的
鎖:
而MySQL是有鎖粒度的。
1.鎖的類型被分爲了讀鎖和寫鎖。
可以這麼想:增刪改就是寫鎖。
查詢纔是讀鎖。
2.鎖的粒度也就是鎖的範圍,分爲表鎖,頁鎖,行鎖。MySQL支持表級鎖,行鎖則需要存儲引擎的支持
MyISAM支持的是表鎖。
DBD支持的頁面鎖,
InnoDB支持的行鎖。
表鎖:開銷小,加鎖快,不會出現死鎖;鎖粒度大,發生鎖衝突的概率最高,併發度最低。
頁面鎖:開銷和加鎖的時間界於表鎖和行鎖之間,會出現死鎖,併發度一般。
行鎖:開銷大,加鎖慢,會出現死鎖;鎖粒度小,發生鎖衝突的概率最低,併發度最高。
鎖的基本概念:
從數據庫角度來看鎖有3類:
1.獨佔鎖:
獨佔鎖的鎖粒度是行或者是多行。只允許鎖定操作的程序使用,其他任何對它的操作均不被接收。當對象有其他鎖的時候,無法對其加獨佔鎖。
2.共享鎖:
共享鎖的鎖粒度是行或者是多行。當被鎖定後其他程序可以讀,但不能修改它。
3.更新鎖:
更新鎖是爲了防止死鎖的。當數據庫要更新數據時,它對數據對象做更新鎖鎖定,這樣數據不能被修改,但可以讀取。等到確定是要更新數據時纔會自動將更新鎖換成獨佔鎖。
從程序員的角度來看鎖有2類:
1.樂觀鎖:
程序員很高興。程序代碼不需要做任何事,數據庫自己加鎖
2.悲觀鎖:
需要程序員自己手動加鎖處理。
隔離性:
數據庫的隔離級別由低到高分別爲Read uncommitted,Read committed,Repeatable read,Serializable。
Read Uncommitted:
讀未提交
例子:老闆要發工資了。程序員一個月的工資是2.6萬/月。但發工資時老闆錯按成了2.9萬。該錢已經打到了程序員的賬戶上,但是事務還沒有提交。這個時候程序員查自己的賬戶發現多了3000元。程序員很高興,以爲漲工資了。但這時老闆發現了這個操作失誤。馬上就回滾了差點提交了的事務。將數據改成了2.6萬後提交。
分析:
實際程序員的工資還是2.6萬。程序員看到了老闆還沒提交的事務。這就是髒讀。
Read Commintted:
讀提交
例子:程序員拿着工資卡去消費(卡里只有2.6萬元),當他要埋單的時候(事務開啓),收費系統檢測卡里有2.6萬元。就在此時。程序員的妻子登錄了程序員的支付寶將所有的錢都轉了出去充當家用,並提交(期間有耗時)。當收費系統準備扣款時,發現卡里沒錢了。程序員就很不理解,卡里明明有錢的啊。
分析:
若有事務對數據進行了更新操作時,讀操作事務必須要等待這個更新操作事務提交了才能讀。這個方法雖然解決了髒讀,但是出現了一個事務中查詢出了兩個不同的數據。這就是不可重複讀。
Repeated read:
重複讀
例子:還是上面的例子。程序員要消費了,當他埋單時。(事務開啓,並不允許其他事務做更新(UPDATE)操作),收費系統檢測到他的卡里有2.6萬元。這個時候程序員妻子想轉出錢是失敗的。接下來收費系統正常的扣錢。完成交易。
分析:
重複讀可以解決不可重複讀,但是這隻解決了update操作。還是有可能出現幻讀的(即插入的Insert操作)。
例子:程序員出去消費,花了2000元。程序員妻子查賬。(全表掃描FTS,妻子事務開啓)看到確實是花了2千元。就在這個時候。程序員花了2萬元買了一臺電腦,瞬間多了一條操作記錄,並立馬提交。當程序員妻子打印程序員賬單時,發現消費成了2.2萬元,感覺好像出了幻覺。這就是幻讀。
Serializable
串行化
這就是最高的隔離性。在該級別下,任何操作必須一個完
MariaDB [CargoWarehouse]> select * from employee_list;+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 180 |
| Zhang | 20 | 2000 |
+-----------+-----+-------+
2 rows in set (0.00 sec)
MariaDB [CargoWarehouse]> commit;
Query OK, 0 rows affected (0.00 sec)
成了才能接着做下一個,輕輕鬆鬆解決髒讀,不可重複讀,和幻讀。但是你知道的。。。效率奇低!!
參考博客:Mysql的事務四個特性以及四個隔離級別
MySQL,Mariadb,Sql Server 一般都是默認Repeatable read。
MariaDB [(none)]> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set (0.00 sec)
接下來實際操作一下。
首先開啓兩個MySQL,看看第一個事務的操作會不會影響第二個事務的操作。
測試read uncommitted
先修改MySQL的隔離級別:
MariaDB [CargoWarehouse]> set tx_isolation='READ-UNCOMMITTED';
Query OK, 0 rows affected (0.00 sec)
MariaDB [CargoWarehouse]> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set (0.00 sec)
兩邊都開啓事務:
MariaDB [CargoWarehouse]> start transaction;
Query OK, 0 rows affected (0.00 sec)
老闆的在事務中的操作:
MariaDB [CargoWarehouse]> insert into employee_list values
-> (
-> 'Zhang san',20,29000
-> );
Query OK, 1 row affected (0.01 sec)
程序員在事務中查看賬戶時發現賬戶上成了29000元:
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 29000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
老闆發現錯了,修改列表:
MariaDB [CargoWarehouse]> update employee_list set wages=26000
-> where name='Zhang san';
Query OK, 1 row affected (0.11 sec)
Rows matched: 1 Changed: 1 Warnings: 0
老闆改完立馬提交:
MariaDB [CargoWarehouse]> commit;
Query OK, 0 rows affected (0.00 sec)
程序員懵逼了,怎麼同一個事務裏查看出了兩個不同的結果:(所謂的髒讀)
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 29000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 26000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
測試read committed
修改隔離級別
(兩邊都需要改)
MariaDB [(none)]> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)
MariaDB [(none)]> start transaction;
Query OK, 0 rows affected (0.00 sec)
程序員要消費,系統檢測卡里的剩餘錢數。
MariaDB [(none)]> select * from CargoWarehouse.employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 26000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
程序員妻子去轉賬。MariaDB [CargoWarehouse]> update employee_list set wages=200 where name='Zhang san';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
程序員這裏再次檢測。(還是26000,沒毛病)
MariaDB [(none)]> select * from CargoWarehouse.employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 26000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
MariaDB [(none)]> select * from CargoWarehouse.employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 26000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
程序員妻子提交申請。
MariaDB [CargoWarehouse]> update employee_list set wages=200 where name='Zhang san';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MariaDB [CargoWarehouse]> commit;
Query OK, 0 rows affected (0.01 sec)
程序員再次查看自己的賬戶。MariaDB [(none)]> select * from CargoWarehouse.employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 26000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
MariaDB [(none)]> select * from CargoWarehouse.employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 26000 |
+-----------+-----+-------+
1 row in set (0.00 sec)
MariaDB [(none)]> select * from CargoWarehouse.employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 200 |
+-----------+-----+-------+
1 row in set (0.00 sec)
程序員再次一臉懵逼,明明在一個事務裏,竟然查詢結果還是不一樣。太不符合事務的特性了測試repeatable read
先修改隔離級別
MariaDB [CargoWarehouse]> set tx_isolation='REPEATABLE-READ';
Query OK, 0 rows affected (0.00 sec)
MariaDB [CargoWarehouse]> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)
MariaDB [CargoWarehouse]> start transaction;
Query OK, 0 rows affected (0.00 sec)
程序員的工資卡只剩200元了,這時程序員妻子開始查賬(並開啓事務)。
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 200 |
+-----------+-----+-------+
1 row in set (0.00 sec)
程序員買了一包煙,並立馬提交了事務。MariaDB [CargoWarehouse]> update employee_list set wages=180 where name='Zhang san';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MariaDB [CargoWarehouse]> commit;
Query OK, 0 rows affected (0.00 sec)
程序員妻子查賬還是200,並沒有因爲程序員的提交,而改變一個事務中的查詢。(事務的隔離性提高了)MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 200 |
+-----------+-----+-------+
1 row in set (0.00 sec)
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 200 |
+-----------+-----+-------+
1 row in set (0.00 sec)
看似這樣做很棒了,但是這隻解決了update,並沒有解決insert。假設
程序員這個月表現的好,發了獎金。(再開個事務做insert操作)
MariaDB [CargoWarehouse]> update employee_list set wages=180 where name='Zhang san';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
MariaDB [CargoWarehouse]> commit;
Query OK, 0 rows affected (0.01 sec)
程序員妻子打印工資單
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 200 |
+-----------+-----+-------+
1 row in set (0.00 sec)
MariaDB [CargoWarehouse]> commit;
Query OK, 0 rows affected (0.00 sec)
MariaDB [CargoWarehouse]> select * from employee_list;
+-----------+-----+-------+
| name | age | wages |
+-----------+-----+-------+
| Zhang san | 20 | 180 |
| Zhang | 20 | 2000 |
+-----------+-----+-------+
2 rows in set (0.00 sec)
串行就不演示了