數據庫事務瞭解一下

介紹

事務 (transaction) 其實指的就是一組操做裏面包含許多單一的邏輯,要麼就是全部邏輯都成功 (提交),只要有一個邏輯沒有成功,那麼這一組操做就算失敗,全部的數據都回歸到最初的狀態(回滾)。mysql

mysql 終端演示事務

現有以下帳戶表,有 id 爲 1 和 2 的兩個帳戶:sql

假如咱們要讓 id 爲 1 的帳戶給 id 爲 2 的帳戶轉 100 塊,咱們要執行的 sql 就是:數據庫

update account set money=money-100 where id=1;
update account set money=money+100 where id=2;

執行完以後:服務器

方式一:關閉事務的自動提交

在上面示例中,其實 mysql 默認就幫咱們提交了事務,只是它把每一條執行的 sql 語句當成了一組操做,也就是執行一條 sql 事務就自動提交。session

而 mysql 事務的自動提交是能夠經過 autocommit 變量設置的:併發

能夠看到它默認是開啓狀態,如今咱們將它關閉:測試

再次執行上面的兩條 sql:spa

這時候會發現表數據好像是正常修改了。其實這只是此時內存中的數據,並無持久化保存到 db,咱們能夠經過 navicat 查看一下表數據:3d

此時咱們再執行一下 commit 提交事務:code

再次使用 navicat 查看錶數據:

此時上述的更新操做就持久化到了 db,這就是事務的提交 (commit) 操做。

假如咱們不執行 commit,執行 rollback

能夠看到數據又回到更新前的初始狀態,這就是事務的回滾 (rollback) 操做。

這種修改自動提交的方式默認只是局部針對當前會話鏈接,對其它鏈接沒有影響,還可設置全局自動提交以下:
set [session] autocommit=[0|1];  // 默認 設置會話級事務自動提交
set global autocommit=[0|1];  // 全局級事務自動提交

方式二:手動開啓事務

除了如方式一修改 mysql 事務的自動提交這種方式,mysql 還爲咱們提供了手動開始事務的方式,以下:

可經過 start transaction 開啓事務:

提交和回滾事務也都是經過 commit 和 rollback 。

以上兩條 DML 語句必須同時成功或者同時失敗,最小單元不可再分。當第一條 DML 語句執行成功後,並不能將底層數據庫中的第一個帳戶的數據修改,只是將操做記錄了一下,這個記錄是在內存中完成的;當第二條 DML 語句執行成功後,和底層數據庫文件中的數據完成同步。若第二條 DML 語句執行失敗,則清空全部的歷史操做記錄,要完成以上的功能必須藉助事務。

事務四大特徵 (ACID)

原子性 (Atomicity):事務是最小單位,不可再分。

一致性 (Consistency):事務要求全部的 DML 語句操做的時候,必須保證同時成功或者同時失敗。

隔離性 (Isolation):事務 A 和事務 B 之間具備隔離性。

持久性 (Durability):是事務的保證,事務終結的標誌(內存的數據持久到硬盤文件中)。

事務的開啓與結束標誌

開啓標誌:
- 任何一條 DML 語句 (insert、update、delete) 執行,標誌事務的開啓。
結束標誌:
- 提交:成功的結束,將全部的 DML 語句操做歷史記錄和底層硬盤數據來一次同步。
- 回滾:失敗的結束,將全部的 DML 語句操做歷史記錄所有清空。

隔離性 (Isolation)

隔離級別

事務 A 和事務 B 之間具備必定的隔離性,有 4 個隔離級別。

  • read uncommitted(讀未提交)

    - 事務 A 和事務 B,事務 A 未提交的數據,事務 B 能夠讀取到。

    - 這裏讀取到的數據叫作「髒數據」。

    - 這種隔離級別最低,這種級別通常是在理論上存在,數據庫隔離級別通常都高於該級別。

  • read committed(讀已提交)

    - 事務 A 和事務 B ,事務A提交的數據,事務 B 才能讀取到。

    - 這種隔離級別高於讀未提交。

    - 換句話說,對方事務提交以後的數據,我當前事務才能讀取到。

    - 這種級別能夠避免「髒數據」。

    - 這種隔離級別會致使「不可重複讀取」。

    - Oracle 默認隔離級別。

  • repeatable read(可重複讀)

    - 事務 A 和事務 B,事務 A 提交以後的數據,事務 B 讀取不到。

    - 事務 B 是可重複讀取數據。

    - 這種隔離級別高於讀已提交。

    - 換句話說,對方提交以後的數據,我仍是讀取不到。

    - 這種隔離級別能夠避免「不可重複讀取」,達到可重複讀取。

    - 好比 1 點和 2 點讀到數據是同一個。

    - Mysql 默認級別。

    - 雖然能夠達到可重複讀取,可是會致使「幻像讀」。

  • serializable(串行化)

    - 事務 A 和事務 B,事務 A 在操做數據庫時,事務 B 只能排隊等待。

    - 這種隔離級別不多使用,吞吐量過低,用戶體驗差。

    - 這種級別能夠避免「幻像讀」,每一次讀取的都是數據庫中真實存在數據,事務 A 與事務 B 串行,而不併發。

- 髒讀:一個事務讀到了另外一個事務未提交的數據。

- 不可重複讀:一個事務讀到了另外一個事務已提交的數據,形成先後兩次查詢結果不一致。

- 幻讀:一個事務讀到了另外一個事務 insert 的數據,形成先後兩次查詢結果不一致(mysql 爲 innoDB 引擎時不存在這個問題)。

隔離級別與一致性關係

隔離級別 髒讀 不可重複讀 幻讀
讀未提交 可能 可能 可能
讀已提交 不可能 可能 可能
可重複讀 不可能 不可能 對 InnoDB 不可能
串行化 不可能 不可能 不可能

設置事務隔離級別

  • 方式一:修改配置文件

    mysql 能夠在 my.ini 文件中使用 transaction-isolation 選項來設置服務器的缺省事務隔離級別。

    值能夠是:
    – READ-UNCOMMITTED – READ-COMMITTED – REPEATABLE-READ – SERIALIZABLE 例: [mysqld] transaction-isolation = READ-COMMITTED
  • 方式二:命令動態設置

    隔離級別也能夠在運行的服務器中動態設置,應使用 SET TRANSACTION ISOLATION LEVEL 語句。
    語法:
    SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
    [GLOBAL]:全局級設置,對全部會話有效。
    [SESSION]:默認級別,會話級設置,只對當前會話有效。 <isolation-level>: – READ UNCOMMITTED – READ COMMITTED – REPEATABLE READ – SERIALIZABLE 例:
    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;

要查看當前事務隔離級別,執行 SELECT @@tx_isolation; 便可:

丟失更新問題

假若有以下帳戶表:

分析:如今同時有 A 和 B 兩個事務操做,A 要修改 id 爲 1 的 name 爲 "zhangsansan",B 要修改 id 爲 1 的 money 爲 "2000"。此時必然有一個事務是先提交的:

假如 A 先提交,此時 id 爲 1 的 name 已修改成 "zhangsansan"。此時問題就出現了,由於 B 的修改信息如今還在內存中,B 這裏的 name 仍是以前的 "zhangsan",提交後就會覆蓋 A 的修改內容,最後將數據修改成:

id    money    name
1     2000   zhangsan

此時,A 的更新內容就丟失了。

悲觀鎖(排他鎖)

用 mysql 測試上面示例,咱們會發現並無出現上述分析狀況,緣由就是排他鎖。

A 窗口,開啓事務,更新數據,但不提交:

B 窗口,開啓事務,執行更新操做,會發現阻塞住了:

當 A 窗口事務提交,B 窗口的阻塞會立馬消失,接着執行完畢。這是由於在 mysql 的事務中執行更新操做時會給要更新的數據加上排他鎖,若是當前更新事務未提交,那麼此時其它事務對該數據的更新操做就會被阻塞至當前事務提交(排他鎖釋放),當前事務提交後,其它事務就能拿到最新的數據在最新數據的基礎上更新,就不會出現分析中丟失更新的狀況。

還能夠經過查詢操做主動給表或行加排他鎖,例如:
select * from account where id=1 for update;

樂觀鎖

與悲觀鎖不一樣,悲觀鎖是由數據庫機制提供,而樂觀鎖是須要開發者手動控制的。修改表結構,給 account 表添加一個 version 字段:

只是要使用樂觀鎖的方式咱們就須要作一些額外的操做,例:

在事務提交以前,須要先檢查當前內存行數據 version 和對應 db 實際行數據 version 是否相同,若是相同,則提交更新,若是當前 version 小於實際 version,就將當前數據更新到實際 version 對應的數據,而後在該數據的基礎上執行咱們本身的更新操做,而且將 version 自增 1 後提交。

依然以上面示例說明,在 A 事務將 id 爲 1 的 name 改成 "zhangsansan" 提交後,該條數據 version 版本此時就爲 1。B 事務接着提交,當它以上述方式檢查當前內存 version 時,會發現當前內存 version 爲 0,而實際對應數據 version 爲 1,它就要將內存數據更新爲 version 爲 1 對應的這個版本了。即 name 同步爲 "zhangsan",接着在這個基礎上執行本身的修改操做,將 money 修改成 2000,因此更新後的結果爲:

id    money     name
1     2000   zhangsansan

這種方式也不會丟失更新,究其根底其實它的原理仍是和悲觀鎖相同:就是保證事務能在最新的數據基礎上更新。

相關文章
相關標籤/搜索