
# 淺談mysql事務
https://zhuanlan.zhihu.com/p/52677680
在技術面試中,面試官經常能夠問到關于數據庫事務相關的,通常數據庫事務具備四大特性(ACID),分別是:
1、原子性
2、一致性
3、隔離性
4、持久性
所謂原子性:是指事務是一個最小單元,不可再分隔,成為一個整體。
所謂一致性:是指事務中的方法要么同時成功,要么都不成功。比如A向B轉賬,要不都成功,要不都失敗。
所謂隔離性:是指當多個事務操作數據庫中同一個記錄或多個記錄時,對事務進行隔離開來有序執行,避免同時對同一數據做操作。這時候就需要使用鎖來解決這個問題了(后面講)。
所謂持久性:即當成功插入一條數據庫記錄時,數據庫必須保證有一條數據永久的寫入到數據庫磁盤中。
我們可以分析一下,事務的四大特征中,所有的操作都會走向磁盤,所以持久性是事務操作的目的,而原子性是實現事務的基礎,隔離性是實現數據安全的一種策略、手段,而最終維護的,就是數據的一致性,一致性才是事務中最重要的。四大特征之間,隔離性是為了達到一致性的手段。
ACID四大特征中,最難理解的不是一致性,而是事務的隔離性,數據庫權威專家針對事務的隔離性研究出來了事務的隔離四種級別,四種事務隔離級別就是為了解決數據在高并發下產生的問題(臟讀、不可重復讀、幻讀)。

Mysql默認使用的數據隔離級別是REPEATABLE READ ,可重復讀,允許幻讀。
~~~text
省查看數據庫默認隔離級別
SELECT @@tx_isolation
~~~
什么是臟讀、不可重復讀、幻讀呢?我們來看看。
## **臟讀**
臟讀:比如有兩個事務并行執行操作同一條數據庫記錄,A事務能讀取到B事務未提交的數據。如下圖所示:事務B操作了數據庫但是沒有提交事務,此時A讀取到了B沒有提交事務的數據。這就是臟讀的體現。

我們用數據庫實例來演示一下臟讀現象,首先我本地安裝了mysql5.7,關閉數據庫自動提交事務開關
~~~text
臨時有效
mysql數據庫事務開關
開啟自動提交事務:set autocommit = 1;
關閉自動提交事務:set autocommit = 0 ;
查看事務開關:show variables like '%autocommit%';
~~~

然后我們修改數據庫的事務級別,命令如下:
~~~text
//全局的 ---此處演示我們設置全局的
set global transaction isolation level read uncommitted;
//當前會話
set session transaction isolation level read uncommitted;
~~~
我們使用Navicat演示一下臟讀,打開兩個窗口,命令如下:
~~~text
/**數據庫建表語句*/
CREATE TABLE `user_money` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_id` bigint(20) NOT NULL COMMENT '用戶id',
`money` decimal(25, 2) NOT NULL COMMENT '余額',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用戶余額表' ROW_FORMAT = Dynamic;
/**插入一條數據*/
INSERT INTO `user_money` VALUES (1, 2628, 1000.00);
~~~
Navicat窗口A,查詢指定用戶信息(此處不提交事務)
~~~text
---------- 窗口A ------------------------------------------
-- 關閉自動提交事務
set autocommit = 0
-- 查詢指定用戶
select * from user_money where user_id = '2628'
-- 修改指定用戶余額
update user_money a set a.money = 500;
-- 提交事務
-- commit;
-- 回滾事務
-- ROLLBACK;
~~~
Navicat窗口B,當窗口A操作完成后再查詢指定用戶信息
~~~text
---------- 窗口B ------------------------------------------
-- 關閉自動提交事務
set autocommit = 0
-- 查詢指定用戶
select * from user_money where user_id = '2628'
~~~
此時我們看看查詢結果:

很顯然,事務A修改了數據后還沒有提交事務,此時事務B可以讀取到事務A沒有提交的事務的數據。這就是臟讀,臟讀情況下的數據是不可取的,所以一般沒有數據庫事務級別設置為允許臟讀。
一句話總結:**臟讀就是指事務A讀取到事務B修改但未提交事務的數據。**
## **不可重復讀**
學習完臟讀后,我們再來看看什么是不可重復讀。比如事務A在同一事務中多次讀取同一記錄,此時事務B修改了事務A正在讀的數據并且提交了事務,但是事務A讀取到了事務B所提交的數據,導致兩次讀取數據不一致。如下圖所示:

然后我們修改數據庫的事務級別,命令如下:
~~~text
//全局的 ---此處演示我們設置全局的
set global transaction isolation level read committed;
//當前會話
set session transaction isolation level read committed;
~~~
我們使用Navicat演示一下臟讀,打開兩個窗口,命令如下:
~~~text
---------- 窗口A ------------------------------------------
-- 關閉自動提交事務
set autocommit = 0
-- 查詢指定用戶
select * from user_money where user_id = '2628'
-- 查詢指定用戶
select * from user_money where user_id = '2628'
-- 提交事務
commit;
~~~
窗口A先是開啟事務,然后查詢指定用戶信息,然后窗口B開啟事務,查詢數據指定用戶,修改數據,提交事務,然后再回到窗口A,查詢指定用戶信息;窗口B代碼如下:
~~~text
---------- 窗口B ------------------------------------------
-- 關閉自動提交事務
set autocommit = 0
-- 查詢指定用戶
select * from user_money where user_id = '2628'
-- 修改指定用戶余額
update user_money a set a.money = 500;
-- 提交事務
commit;
~~~

事務A在兩次查詢中,查詢的數據不一樣,這就是不可重復讀。Mysql默認采用的就是不可重復讀的隔離級別,用一句話總結,**不可重復讀就是事務A讀取到事務B已提交事務的數據,導致兩次讀取數據信息不一致。**
## **幻讀**
上面我我們學習了一下什么不可重復讀,在mysql數據庫中,不可重復讀是不被允許的,mysql默認的隔離級可重復讀,也就是幻讀。
下面我們再來看看幻讀,什么是幻讀呢?
\-------------------------2020年5月31日 10:11:42------------------
之前關于幻讀這一塊描述有問題,此處進行更正;
從字面上看幻讀二字,更多的感覺像是讀取到了一個虛幻的假象。
幻讀,并不是說兩次讀取獲取的結果集不同,幻讀側重的方面是某一次的 select 操作得到的結果所表征的數據狀態無法支撐后續的業務操作。
更為具體一些:select 某記錄是否存在,不存在,準備插入此記錄,但執行 insert 時發現此記錄已存在,無法插入,此時就發生了幻讀。

幻讀
然后我們修改數據庫的事務級別,命令如下:
~~~text
//全局的 ---此處演示我們設置全局的
set global transaction isolation level repeatable read;
//當前會話
set session transaction isolation level repeatable read;
~~~
我們使用Navicat演示一下臟讀,打開兩個窗口,命令如下:
~~~text
-- -------- 窗口A ------------------------------------------
-- 關閉自動提交事務
set autocommit = 0;
select * from user_money where id = 5;
-- 沒有查詢到結果,進行插入
insert into user_money (id, user_id , money) values (5, 5,5);
-- 再查詢id為5的
select * from user_money where id = 5;
commit;
~~~
此處,事務A中查詢指定id為5的記錄,沒有查詢到結果后,事務B進行插入了一條id為5的記錄并且提交事務;
~~~text
---------- 窗口B ------------------------------------------
-- 關閉自動提交事務
set autocommit = 0;
-- 插入一條記錄
insert into user_money (id, user_id , money) values (5, 5,5);
-- 提交事務
commit;
~~~
此時事務A執行插入一句會報錯主鍵沖突,但是再進行查詢的時候又查詢不到

在上述事務A中,不提交事務的情況下,插入id為5的記錄會一直報錯主鍵沖突,但是再怎么查詢id為5的記錄都查詢不到;這是因為在MySql的設計中,事務中查詢的是被修改前的日志,即Undo log(MVCC中的一個概念);可參考
[加耀:Mysql中InnoDB學習MVCC知識點總結?zhuanlan.zhihu.com](https://zhuanlan.zhihu.com/p/75825571)
關于幻讀的問題,可以通過加鎖的方式進行解決。
## **串行化**
另外還有一種事務級別就是序列化方式SERIALIZABLE,序列化事務級別既不允許臟讀,也不允許不可重復讀,并且還不允許幻讀。
事務隔離級別越嚴格,越消耗計算機性能,效率也越低,通常情況下,設置為允許不可重復讀就可以解決大多數的問題了。
這里,我們對數據庫事務基本上已經有了一個新的認識了。那么,數據庫是怎么來隔離事務的呢?這時候就涉及到了數據庫鎖了。這個我們在下一章節中會詳細描述的。
2020年5月31日 10:45:10
- 開始
- 公益
- 更好的使用看云
- 推薦書單
- 優秀資源整理
- 技術文章寫作規范
- SublimeText - 編碼利器
- PSR-0/PSR-4命名標準
- php的多進程實驗分析
- 高級PHP
- 進程
- 信號
- 事件
- IO模型
- 同步、異步
- socket
- Swoole
- PHP擴展
- Composer
- easyswoole
- php多線程
- 守護程序
- 文件鎖
- s-socket
- aphp
- 隊列&并發
- 隊列
- 講個故事
- 如何最大效率的問題
- 訪問式的web服務(一)
- 訪問式的web服務(二)
- 請求
- 瀏覽器訪問阻塞問題
- Swoole
- 你必須理解的計算機核心概念 - 碼農翻身
- CPU阿甘 - 碼農翻身
- 異步通知,那我要怎么通知你啊?
- 實時操作系統
- 深入實時 Linux
- Redis 實現隊列
- redis與隊列
- 定時-時鐘-阻塞
- 計算機的生命
- 多進程/多線程
- 進程通信
- 拜占庭將軍問題深入探討
- JAVA CAS原理深度分析
- 隊列的思考
- 走進并發的世界
- 鎖
- 事務筆記
- 并發問題帶來的后果
- 為什么說樂觀鎖是安全的
- 內存鎖與內存事務 - 劉小兵2014
- 加鎖還是不加鎖,這是一個問題 - 碼農翻身
- 編程世界的那把鎖 - 碼農翻身
- 如何保證萬無一失
- 傳統事務與柔性事務
- 大白話搞懂什么是同步/異步/阻塞/非阻塞
- redis實現鎖
- 淺談mysql事務
- PHP異常
- php錯誤
- 文件加載
- 路由與偽靜態
- URL模式之分析
- 字符串處理
- 正則表達式
- 數組合并與+
- 文件上傳
- 常用驗證與過濾
- 記錄
- 趣圖
- foreach需要注意的問題
- Discuz!筆記
- 程序設計思維
- 抽象與具體
- 配置
- 關于如何學習的思考
- 編程思維
- 談編程
- 如何安全的修改對象
- 臨時
- 臨時筆記
- 透過問題看本質
- 程序后門
- 邊界檢查
- session
- 安全
- 王垠
- 第三方數據接口
- 驗證碼問題
- 還是少不了虛擬機
- 程序員如何談戀愛
- 程序員為什么要一直改BUG,為什么不能一次性把代碼寫好?
- 碎碎念
- 算法
- 實用代碼
- 相對私密與絕對私密
- 學習目標
- 隨記
- 編程小知識
- foo
- 落盤
- URL編碼的思考
- 字符編碼
- Elasticsearch
- TCP-IP協議
- 碎碎念2
- Grafana
- EFK、ELK
- RPC
- 依賴注入
- 科目一
- 開發筆記
- 經緯度格式轉換
- php時區問題
- 解決本地開發時調用遠程AIP跨域問題
- 后期靜態綁定
- 談tp的跳轉提示頁面
- 無限分類問題
- 生成微縮圖
- MVC名詞
- MVC架構
- 也許模塊不是唯一的答案
- 哈希算法
- 開發后臺
- 軟件設計架構
- mysql表字段設計
- 上傳表如何設計
- 二開心得
- awesomes-tables
- 安全的代碼部署
- 微信開發筆記
- 賬戶授權相關
- 小程序獲取是否關注其公眾號
- 支付相關
- 提交訂單
- 微信支付筆記
- 支付接口筆記
- 支付中心開發
- 下單與支付
- 支付流程設計
- 訂單與支付設計
- 敏感操作驗證
- 排序設計
- 代碼的運行環境
- 搜索關鍵字的顯示處理
- 接口異步更新ip信息
- 圖片處理
- 項目搭建
- 閱讀文檔的新方式
- mysql_insert_id并發問題思考
- 行鎖注意事項
- 細節注意
- 如何處理用戶的輸入
- 不可見的字符
- 抽獎
- 時間處理
- 應用開發實戰
- python 學習記錄
- Scrapy 教程
- Playwright 教程
- stealth.min.js
- Selenium 教程
- requests 教程
- pyautogui 教程
- Flask 教程
- PyInstaller 教程
- 蜘蛛
- python 文檔相似度驗證
- thinkphp5.0數據庫與模型的研究
- workerman進程管理
- workerman網絡分析
- java學習記錄
- docker
- 筆記
- kubernetes
- Kubernetes
- PaddlePaddle
- composer
- oneinstack
- 人工智能 AI
- 京東
- pc_detailpage_wareBusiness
- doc
- 電商網站設計
- iwebshop
- 商品規格分析
- 商品屬性分析
- tpshop
- 商品規格分析
- 商品屬性分析
- 電商表設計
- 設計記錄
- 優惠券
- 生成唯一訂單號
- 購物車技術
- 分類與類型
- 微信登錄與綁定
- 京東到家庫存系統架構設計
- crmeb
- 命名規范
- Nginx https配置
- 關于人工智能
- 從人的思考方式到二叉樹
- 架構
- 今日有感
- 文章保存
- 安全背后: 瀏覽器是如何校驗證書的
- 避不開的分布式事務
- devops自動化運維、部署、測試的最后一公里 —— ApiFox 云時代的接口管理工具
- 找到自己今生要做的事
- 自動化生活
- 開源與漿果
- Apifox: API 接口自動化測試指南