Redis的事務(wù)是基于單線程的兩階段不完全事務(wù)(主要是不支持原子性),它僅僅是保證事務(wù)里的操作會被連續(xù)獨(dú)占的執(zhí)行。因?yàn)槭?strong>單線程架構(gòu),在執(zhí)行完事務(wù)內(nèi)所有指令前是不可能再去同時執(zhí)行其他客戶端的請求的。
0- 為什么是單線程
Redis作者追求簡單,簡單才不會出錯,使得代碼不用處理平時最讓人頭痛的并發(fā)而大幅簡化,作者認(rèn)為CPU不是瓶頸,內(nèi)存與網(wǎng)絡(luò)帶寬才是,當(dāng)top看到單個CPU 100%時,單核CPU稱為瓶頸,此時就是垂直擴(kuò)展的時候了。
1- Redis事務(wù)ACID
我們知道Mysql等傳統(tǒng)關(guān)系型數(shù)據(jù)庫一般都滿足ACID,下面來看一看Redis是否滿足ACID
- 原子性(Atomicity)
它不保證原子性——所有指令同時成功或同時失敗,只有決定是否開始執(zhí)行全部指令的能力,沒有執(zhí)行到一半進(jìn)行回滾的能力。當(dāng)系統(tǒng)突然宕機(jī),Mysql也能根據(jù)binlog恢復(fù),而Redis不可以。
- 一致性(Consistency)
redis 事務(wù)在執(zhí)行過程中發(fā)生錯誤或進(jìn)程被終結(jié),都能保證數(shù)據(jù)的一致性(Consistency)。因?yàn)閞edis 使用單線程串行方式來執(zhí)行事務(wù)的,在執(zhí)行事務(wù)期間不會對事務(wù)進(jìn)行中斷(一致性)。
- 隔離性(Isolation)
它沒有隔離級別的概念,因?yàn)槭聞?wù)提交前任何指令都不會被實(shí)際執(zhí)行,也就不存在”事務(wù)內(nèi)的查詢要看到事務(wù)里的更新,在事務(wù)外查詢不能看到”這個讓人萬分頭痛的問題,所以滿足隔離性(Isolation)。
- 持久性(Durability)
但當(dāng)redis 服務(wù)器使用AOF 持久化模式并appendfsync 設(shè)置為always 時,程序執(zhí)行操作命令后會調(diào)用sync 函數(shù)將數(shù)據(jù)保存到硬盤里,因此redis 事務(wù)也可以具有持久性(Durability)。
2- 事務(wù)實(shí)現(xiàn):Mysql vs Redis
Mysql
- 悲觀鎖
是通過select * from table where for update將數(shù)據(jù)加鎖,導(dǎo)致其他線程或事務(wù)不能更新該數(shù)據(jù)。
- 樂觀鎖
- InnoDB基于系統(tǒng)版本號實(shí)現(xiàn)樂觀鎖(只在可重復(fù)讀和提交讀隔離級別下工作),每行記錄后面保存了隱藏兩個列,一個是創(chuàng)建或者修改這條記錄的時候當(dāng)前系統(tǒng)的版本號(每次操作都將版本號加1),一個是刪除此行記錄是當(dāng)前系統(tǒng)的版本號,一個記錄修改,一個記錄刪除標(biāo)識。
- 當(dāng)讀取數(shù)據(jù)時,將版本標(biāo)識的值一同讀出,數(shù)據(jù)每更新一次,同時對版本標(biāo)識進(jìn)行更新。當(dāng)我們提交更新的時候,判斷數(shù)據(jù)庫表對應(yīng)記錄的當(dāng)前版本信息與第一次取出來的版本標(biāo)識進(jìn)行比對,如果數(shù)據(jù)庫表當(dāng)前版本號與第一次取出來的版本標(biāo)識值相等,則予以更新,否則認(rèn)為是過期數(shù)據(jù),事務(wù)失敗。
Redis
redis 是通過watch 命令監(jiān)控?cái)?shù)據(jù)是否發(fā)生變化實(shí)現(xiàn)樂觀鎖的。當(dāng)watch 的對象被更改,比如某個list已被別的客戶端push/pop過了,會導(dǎo)致事務(wù)放棄執(zhí)行。
3- 事務(wù):Mysql vs Redis
不同場景下Mysql(InnoDB存儲引擎)事務(wù)與Redis事務(wù)的比較
背景:tom1 賬戶當(dāng)前有1000元,tom2 賬戶當(dāng)前有500元
場景1:使用事務(wù)從tom1轉(zhuǎn)100元到tom2

mysql 開啟事務(wù)后事務(wù)中的sql 語句在commit 之前就已經(jīng)執(zhí)行了sql 語句的(同一事務(wù)內(nèi)可重復(fù)讀,InnoDB默認(rèn)隔離級別),只是并未真正提交到數(shù)據(jù)庫。
redis 使用multi 開啟事務(wù)后,編寫的sql 語句都進(jìn)入queue 隊(duì)列中(Redis沒有隔離級別的概念,sever單線程模式,不會并發(fā)讀),待執(zhí)行exec 提交事務(wù)時才一次性按進(jìn)入queue 隊(duì)列的順序提交到數(shù)據(jù)庫。
同樣針對回滾,mysql 執(zhí)行rollback 會將提交的數(shù)據(jù)回滾,redis 因?yàn)闆]有提交到數(shù)據(jù),使用discard 只是單純?nèi)∠趒ueue 中的sql 語句。
場景2: 在執(zhí)行tom1 向tom2 轉(zhuǎn)過程中100元的過程中使用了語法錯誤的sql。

mysql 事務(wù)即使遇到錯誤的語句也會提交正確的sql 到數(shù)據(jù)庫(正確語句提交,發(fā)生錯誤的語句拋出異常,但是不影響事務(wù)的提交),需要程序員控制當(dāng)遇到語句異常時進(jìn)行回滾。
redis 與mysql 不同,提交事務(wù)時會先檢查queue 中有語法是否有錯誤,如果有語法錯誤則會discard 整個事務(wù)中操作命令語句。
場景3: 在執(zhí)行tom1 向tom2 轉(zhuǎn)過程中100元的過程中使用了錯誤的執(zhí)行對象。

mysql 和redis 事務(wù)當(dāng)遇到操作對象類型不正確的時候都會提交執(zhí)行事務(wù),但是Mysql可以檢測這個錯誤然后通知客戶端,如果客戶端要求錯誤回滾,則能將已經(jīng)執(zhí)行操作回滾,而Redis沒有這個權(quán)限:只有決定是否開始執(zhí)行全部指令的能力,沒有執(zhí)行到一半進(jìn)行回滾的能力。