MySQL 事務(wù)的概念及ACID屬性和使用詳解
一、什么是事務(wù)
- 定義:由一條或者多條
sql
語句構(gòu)成的sql
集合體,這個集合體合在一起共同要完成某種任務(wù)。MySQL通過多線程實現(xiàn)存儲工作,因此在并發(fā)訪問場景中,事務(wù)確保了數(shù)據(jù)操作的一致性和可靠性。 事務(wù)還規(guī)定 不同的客戶端看到的數(shù)據(jù)是不相同的- 事務(wù)就是要做的或所做的事情,主要用于 處理操作量大,復(fù)雜度高的數(shù)據(jù)
- 假設(shè)一種場景:你畢業(yè)了, 學(xué)校的教務(wù)系統(tǒng)后臺 MySQL 中,不在需要你的數(shù)據(jù),要刪除你的所有信息(一般不會:) ), 那么要刪除你的 基本信息(姓名,電話,籍貫等)的同時,也刪除和你有關(guān)的其他信息,比如:你的各科成績,你在校表 現(xiàn),甚至你在論壇發(fā)過的文章等。這樣,就需要多條 MySQL 語句構(gòu)成,那么所有這些操作合起來,就構(gòu)成了一個事務(wù)。
- 背景:在沒有控制的情況下進(jìn)行
CURD
操作(創(chuàng)建、更新、讀取、刪除)可能導(dǎo)致數(shù)據(jù)不一致的問題。例如,在火車票售票系統(tǒng)中,兩個用戶同時嘗試購買最后一張票,可能造成同一張票被賣出兩次的現(xiàn)象
CURD滿足什么屬性,能解決上述問題?
- 買票的過程得是原子的吧
- 買票互相應(yīng)該不能影響吧
- 買完票應(yīng)該要永久有效吧
- 買前,和買后都要是確定的狀態(tài)吧
二、事務(wù)的屬性及使用
2.1 事務(wù)的 ACID 屬性
一個 MySQL 數(shù)據(jù)庫,可不止你一個事務(wù)在運行,同一時刻,甚至有大量的請求被包裝成 事務(wù),在向 MySQL 服務(wù)器發(fā)起事務(wù)處理請求。而每條事務(wù)至少一條 SQL
,最多很多 SQL
;這樣如果大 家都訪問同樣的表數(shù)據(jù),在不加保護(hù)的情況,就絕對會出現(xiàn)問題。甚至,因為事務(wù)由多條 SQL 構(gòu)成,那 么,也會存在執(zhí)行到一半出錯或者不想再執(zhí)行的情況,那么已經(jīng)執(zhí)行的怎么辦呢?
所有,一個完整的事務(wù),絕對不是簡單的 sql
集合,還需要滿足如下四個屬性:
- 原子性(Atomicity,或稱不可分割性):事務(wù)的所有操作要么全部完成,要么完全不執(zhí)行,任何一部分失敗都會導(dǎo)致整個事務(wù)的回滾。
- 一致性(Consistency):事務(wù)前后,數(shù)據(jù)庫應(yīng)保持一致的狀態(tài),即事務(wù)不應(yīng)破壞數(shù)據(jù)庫的完整性約束。(通過原子性,隔離性,持久性 AND 用戶的配合實現(xiàn)一致性)
- 這表示寫入的資料必須完 全符合所有的預(yù)設(shè)規(guī)則,這包含資料的精確度、串聯(lián)性以及后續(xù)數(shù)據(jù)庫可以自發(fā)性地完成預(yù)定的工 作。
- 隔離性(Isolation,又稱獨立性):事務(wù)之間的執(zhí)行是相互隔離的,一個事務(wù)的執(zhí)行不會受到其他事務(wù)的影響。
- 允許多個并發(fā)事務(wù)同時對其數(shù)據(jù)進(jìn)行讀寫和修改的能力,隔離性可以防止多個事務(wù) 并發(fā)執(zhí)行時由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。
- 事務(wù)隔離分為不同級別,包括讀未提交(
Read uncommitted
)、讀提交(read committed
)、可重復(fù)讀(repeatable read
)和串行化 (Serializable
)
- 持久性(Durability):一旦事務(wù)提交,其對數(shù)據(jù)庫所做的改變將是 永久性 的,即使系統(tǒng)發(fā)生故障也不會丟失。
2.2 為什么存在事務(wù)
剛才說的是多個 sql
在 交叉執(zhí)行 可能會出現(xiàn) 并發(fā)問題 ,進(jìn)而導(dǎo)致數(shù)據(jù)不一致,進(jìn)而導(dǎo)致數(shù)據(jù)完整性,這都知道。但是對事務(wù)的理解不能光站在程序員角度理解,一定要站在數(shù)據(jù)庫使用者角度考慮。
- 事務(wù)被
MySQL
編寫者設(shè)計出來,但是 事務(wù) 并不是天然就有的,而是在用一段時間發(fā)現(xiàn)要有這個 事務(wù)。本質(zhì)是為了當(dāng)應(yīng)用程序訪問數(shù)據(jù)庫的時候,事務(wù) 能夠簡化我們的編程模型,不需要我們?nèi)タ紤]各種各樣的潛在錯誤和并發(fā)問題。 - 只需要把告訴我你要干什么,把你的
sql
給我,我?guī)湍惴庋b成事務(wù),幫你去運行??梢韵胍幌庐?dāng)我們使用事務(wù)時,要么提交,要么回滾,我們不會去考慮網(wǎng)絡(luò)異常了,服務(wù)器宕機了,同時更改一個數(shù)據(jù)怎么辦對吧?這些問題統(tǒng)統(tǒng)不考慮。 - 因此 事務(wù) 本質(zhì)上是為了應(yīng)用層服務(wù)的,是為了讓上層的應(yīng)用服務(wù)更好的使用數(shù)據(jù)庫。而不是伴隨著數(shù)據(jù)庫系統(tǒng)天生就有的。
備注:我們后面把 MySQL
中的一行信息,稱為 一行記錄
總結(jié)
- 解決并發(fā)問題:事務(wù)的設(shè)計初衷是為了應(yīng)對并發(fā)操作帶來的數(shù)據(jù)不一致問題,確保數(shù)據(jù)的完整性和一致性。
- 服務(wù)應(yīng)用層:事務(wù)的本質(zhì)是為應(yīng)用層服務(wù)的,它簡化了開發(fā)者的編程模型,使開發(fā)者可以專注于業(yè)務(wù)邏輯,而不用擔(dān)心底層的數(shù)據(jù)操作細(xì)節(jié)。
- 對來的一大批 SQL–打包成事務(wù)-- 先描述再組織 管理
2.3 事務(wù)的版本支持
- 支持引擎:在MySQL中,只有使用了
InnoDB
數(shù)據(jù)庫引擎的數(shù)據(jù)庫或表才 - 支持事務(wù),
MyISAM
不支持。
查看引擎:
mysql> show engines \G; # ... *************************** 5. row *************************** Engine: MyISAM Support: YES Comment: MyISAM storage engine Transactions: NO XA: NO Savepoints: NO # ... *************************** 7. row *************************** Engine: InnoDB Support: DEFAULT Comment: Supports transactions, row-level locking, and foreign keys Transactions: YES XA: YES Savepoints: YES # ... 9 rows in set (0.01 sec)
2.4 事務(wù)的提交方式
- 自動提交:事務(wù)默認(rèn)是自動提交的。
- 手動提交
查看:
show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+
設(shè)置:
set autocommit=0; -- 設(shè)置為手動提交 set autocommit=1; -- 設(shè)置為自動提交
2.5 事務(wù)的常見操作方式
linux下 mysql
是一個命令行式的客戶端進(jìn)程。但不僅如此 mysql
客戶端還有很多其他客戶端如圖形化界面版的,還有其他語言版的。
mysql
是一套網(wǎng)絡(luò)服務(wù)進(jìn)程也就意味著除了本地主機,遠(yuǎn)端主機也可以連接myql
換句話說 mysql
服務(wù)器可能會被多個客戶端同時訪問
root@VM-8-10-ubuntu:/home/lighthouse# netstat -nltp; # 需要切換成 root 用戶 Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 13847/mysqld tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 823/sshd: /usr/sbin
為了更好做事務(wù)方面演示,我們將 mysql
的默認(rèn)隔離級別設(shè)置成讀未提交,隔離級別后面我們專門具體說。
設(shè)置全局事務(wù)隔離級別 讀為提交
set global transaction isolation level READ UNCOMMITTED;
需要重啟終端,進(jìn)行查看,可以看到
mysql> quit Bye # 需要重啟終端,進(jìn)行查看 mysql> select @@tx_isolation; # 5.0 版本 mysql> select @@transaction_isolation; # 8.0 版本 +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-UNCOMMITTED | +-------------------------+ REPEATABLE-READ # 默認(rèn)是這個
- 然后新起兩個
mysql
客戶端,為什么這么做呢,我們主要是為了研究事務(wù),研究事務(wù)就要研究多個客戶端并發(fā)訪問的情況。 - 其次
mysql
是有隔離性和隔離級別的,所以目前把隔離級別跳到最低,一個mysql
做操作,另一個mysql
就能看到。 - 這樣很能清楚看到事務(wù)交叉所帶來的問題。
【案例】:
① 創(chuàng)建一個員工表
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;
兩個 mysql客戶端 可以并發(fā)訪問這張表,準(zhǔn)備工作全部就緒,下面我們來做試驗!
mysql> show processlist; +------+-----------------+-----------+--------+---------+---------+------------------------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +------+-----------------+-----------+--------+---------+---------+------------------------+------------------+ | 5 | event_scheduler | localhost | NULL | Daemon | 4481367 | Waiting on empty queue | NULL | | 2385 | root | localhost | learn3 | Query | 0 | init | show processlist | | 2386 | root | localhost | learn3 | Sleep | 41 | | NULL | +------+-----------------+-----------+--------+---------+---------+------------------------+------------------+
?正常演示 - 事務(wù)的開始與回滾
② 啟動事務(wù)
start transaction; -- 方式一 begin; -- 方式二
③ 設(shè)置保存點
savepoint s1;
④ 回滾到保存點
rollback s1;
設(shè)置和回滾使用如下:
⑤ 提交事務(wù)
如果想結(jié)束這個事務(wù),那就提交一下,相當(dāng)于把這個事務(wù)提交了
commit;
⑥ 回滾事務(wù)
- 那我們以后是不是必須要設(shè)置保持點才能回滾呢?
- 并不是!沒有保存點只是沒有辦法定向回滾了。但是可以直接回滾到最開始!
rollback;
直接回滾到最開始!然后數(shù)據(jù)就全沒了。即使是結(jié)束事務(wù),回歸到單 sql
也是沒有的。
非正常操作
下面都是默認(rèn)開啟 自動提交 的,而且 演示 1 和 演示 2 表開始的時候均無數(shù)據(jù)(empty
)
① 非正常演示1: 證明未 commit
,客戶端崩潰,MySQL自動會回滾(隔離級別設(shè)置為讀未提交)
-- 終端A begin; -- 開啟事務(wù) insert into account values (1, '張三', 100); -- 插入記錄 mysql> select * from account; -- 數(shù)據(jù)已經(jīng)存在,但沒有commit,此時同時查看終端B +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 張三 | 100.00 | +----+--------+--------+
讓 A 中止,然后再查看A終止前后 B 的數(shù)據(jù),如下:
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; -- 終端 A 崩潰后 Empty set (0.00 sec)
上面我們發(fā)現(xiàn) 數(shù)據(jù)自動進(jìn)行了回滾
② 非正常演示2 - 證明 commit
了,客戶端崩潰,MySQL數(shù)據(jù)不會在受影響,已經(jīng)持久化
-- 終端 A mysql> begin; -- 開啟事務(wù) mysql> insert into account values (1, '張三', 100); -- 插入記錄 Query OK, 1 row affected (0.00 sec) mysql> commit; -- 提交事務(wù) Query OK, 0 rows affected (0.04 sec) mysql> Aborted -- ctrl + \ 異常終止MySQL -- 終端 A 終止后,切換終端 B 查看數(shù)據(jù) mysql> select * from account; +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 張三 | 100.00 | +----+--------+--------+ 1 row in set (0.00 sec)
相比于上面這里我們在異常終止終端 A 之前,已經(jīng)把數(shù)據(jù) commit
了,然后再查看 B 發(fā)現(xiàn)數(shù)據(jù)還在,沒有回滾 – 持久化保存
- 數(shù)據(jù)存在了,所以
commit
的作用是將數(shù)據(jù)持久化到MySQL中
③ 非正常演示3 - 對比演示 1。證明 begin
操作會自動更改提交方式,不會受 MySQL
是否自動提交影響
-- 終端 A,歷史是有數(shù)據(jù)的 mysql> select * from account; +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 張三 | 100.00 | +----+--------+--------+ -- 事務(wù) A 開始時: 是默認(rèn)自動提交的查 mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) -- 關(guān)閉自動提交 mysql> set autocommit=0; mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+
然后再開始進(jìn)行事務(wù)操作,如下:
mysql> begin; -- 開啟事務(wù) mysql> insert into account values (2, '李四', 10000); -- 插入記錄 -- 在終端 A 中查看插入記錄 mysql> select *from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+
然后再次讓終端 A 異常終止,查看 A 終止前后 B 的數(shù)據(jù),如下:
mysql> Aborted -- 再次異常終止 A -- 切換到終端 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 | +----+--------+--------+
此時我們發(fā)現(xiàn),這個結(jié)果和 演示1 是類似的,可以知道 begin
與是否設(shè)置 set autocommit
無關(guān),證明正確
④ 非正常演示4 - 證明單條 SQL 與事務(wù)的關(guān)系【autocommit】
實驗一:
-- 終端 A,開始時是有數(shù)據(jù)的 mysql> select * from account; +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 張三 | 100.00 | +----+--------+--------+ mysql> set autocommit=0; -- 關(guān)閉自動提交 mysql> insert into account values (2, '李四', 10000); -- 插入記錄 Query OK, 1 row affected (0.00 sec) mysql> select *from account; -- 在終端 A 中查看結(jié)果,已經(jīng)插入。此時可以在查看終端B +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> ^D Bye -- 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 | +----+--------+--------+
上面數(shù)據(jù)發(fā)生了回滾
實驗二:
-- 終端A mysql> show variables like 'autocommit'; -- 開啟默認(rèn)提交 +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) mysql> insert into account values (2, '李四', 10000); mysql> select * from account; -- 數(shù)據(jù)已經(jīng)插入 +----+--------+----------+ | 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 | +----+--------+----------+ mysql> select * from account; -- 終端A崩潰后,并不影響,已經(jīng)持久化。autocommit起作用 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 張三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+
結(jié)論:
- 未提交時客戶端崩潰:事務(wù)可以手動回滾,同時操作異常時,
MySQL
會自動回滾未提交的事務(wù)。 - 已提交時客戶端崩潰:只要輸入
begin
或者start transaction
,事務(wù)便必須要通過commit
提交,才會持久化,與是否設(shè)置set autocommit
無關(guān) - 單條SQL與事務(wù):默認(rèn)情況下,對于
InnoDB
每一條 SQL 語言都默認(rèn)封裝成事務(wù),自動提交。如果關(guān)閉自動提交,單條SQL語句也需要手動提交或回滾【select
有特殊情況,因為 MySQL 有 MVCC】
從上面的例子,我們能看到事務(wù)本身的原子性(回滾),持久性(commit)
注意事項
- 回滾:如果沒有設(shè)置保存點,只能回滾到事務(wù)的開始,直接使用 rollback(前提是事務(wù) 還沒有提交)
- 提交:事務(wù)提交后無法回滾。
- 保存點:可以選擇回退到哪個保存點。
- 版本支持:
InnoDB
支持事務(wù),MyISAM
不支持事務(wù) - 啟動事務(wù):可以使用
start transaction
或begin
到此這篇關(guān)于MySQL 事務(wù)的概念及ACID屬性和使用詳解的文章就介紹到這了,更多相關(guān)mysql事務(wù)ACID屬性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MySQL數(shù)據(jù)庫備份和還原的常用命令小結(jié)
MySQL數(shù)據(jù)庫備份和還原的常用命令小結(jié),學(xué)習(xí)mysql的朋友可以參考下2012-03-03