# 不支持事務
我們繼續上一章節的內容,大家應該記得我們`Lua`代碼中是如何完成`ngx_postgres`模塊調用的。我們把他簡單改造一下,讓他更接近真實代碼。
~~~
local json = require "cjson"
function db_exec(sql_str)
local res = ngx.location.capture('/postgres',
{ args = {sql = sql_str } }
)
local status = res.status
local body = json.decode(res.body)
if status == 200 then
status = true
else
status = false
end
return status, body
end
-- 轉賬操作,對ID=100的用戶加10,同時對ID=200的用戶減10。
? local status
? status = db_exec("BEGIN")
? if status then
? db_exec("ROLLBACK")
? end
?
? status = db_exec("UPDATE ACCOUNT SET MONEY=MONEY+10 WHERE ID = 100")
? if status then
? db_exec("ROLLBACK")
? end
?
? status = db_exec("UPDATE ACCOUNT SET MONEY=MONEY-10 WHERE ID = 200")
? if status then
? db_exec("ROLLBACK")
? end
?
? db_exec("COMMIT")
~~~
后面這部分有問題的代碼,在沒有并發的場景下使用,是不會有任何問題的。但是這段代碼在高并發應用場景下,錯誤百出。你會發現最后執行結果完全摸不清楚。明明是個轉賬邏輯,一個收入,一直支出,最后卻發現總收入比支出要大。如果這個錯誤發生在金融領域,那不知道要賠多少錢。
如果你能靠自己很快明白錯誤的原因,那么恭喜你你對數據庫連接、`Nginx`機理都是比較清楚的。如果你想不明白,那就聽我給你掰一掰這面的小知識。
數據庫的事物成功執行,事物相關的所有操作是必須執行在一條連接上的。`SQL`的執行情況類似這樣:
~~~
連接:`BEGIN` -> `SQL(UPDATE、DELETE... ...)` -> `COMMIT`。
~~~
但如果你創建了兩條連接,每條連接提交的SQL語句是下面這樣:
~~~
連接1:`BEGIN` -> `SQL(UPDATE、DELETE... ...)`
連接2:`COMMIT`
~~~
這時就會出現連接1的內容沒有被提交,行鎖產生。連接2提交了一個空的`COMMIT`。
說到這里你可能開始鄙視我了,誰瘋了非要創建兩條連接來這么用SQL啊。有麻煩,又不好看,貌似從來沒聽說過還有人在一次請求中創建多個數據庫連接,簡直就是非人類嘛。
或許你不會主動、顯示的創建多個連接,但是剛剛的示例代碼,高并發下這個事物的每個SQL語句都可能落在不同的連接上。為什么呢?這是因為通過`ngx.location.capture`跳轉到`/postgres`小節后,`Nginx`每次都會從連接池中挑選一條空閑連接,二當時那條連接是空閑的,完全沒法預估。所以上面的第二個例子,就這么靜悄悄的發生了。如果你不了解`Nginx`的機理,那么他肯定會一直困擾你。為什么一會兒好,一會兒不好。
同樣的道理,我們推理到`DrizzleNginxModule`、`RedisNginxModule`、`Redis2NginxModule`,他們都是無法做到在兩次連續請求落到同一個連接上的。
由于這個`Bug`藏得比較深,并且不太好講解,所以我覺得生產中最好用`lua-resty-*`這類的庫,更符合標準調用習慣,直接可以繞過這些坑。不要為了一點點的性能,犧牲了更大的蛋糕。看得見的,看不見的,都要了解用用,最后再做決定,肯定不吃虧。
- 序
- Lua簡介
- Lua環境搭建
- 基礎數據類型
- 表達式
- 控制結構
- if/else
- while
- repeat
- 控制結構for的使用
- break,return
- Lua函數
- 函數的定義
- 函數的參數
- 函數的返回值
- 函數回調
- 模塊
- String庫
- Table庫
- 日期時間函數
- 數學庫函數
- 文件操作
- 元表
- 面向對象編程
- FFI
- LuaRestyRedisLibrary
- select+set_keepalive組合操作引起的數據讀寫錯誤
- redis接口的二次封裝(簡化建連、拆連等細節)
- redis接口的二次封裝(發布訂閱)
- pipeline壓縮請求數量
- script壓縮復雜請求
- LuaCjsonLibrary
- json解析的異常捕獲
- 稀疏數組
- 空table編碼為array還是object
- 跨平臺的庫選擇
- PostgresNginxModule
- 調用方式簡介
- 不支持事務
- 超時
- 健康監測
- SQL注入
- LuaNginxModule
- 執行階段概念
- 正確的記錄日志
- 熱裝載代碼
- 阻塞操作
- 緩存
- sleep
- 定時任務
- 禁止某些終端訪問
- 請求返回后繼續執行
- 調試
- 調用其他C函數動態庫
- 我的lua代碼需要調優么
- 變量的共享范圍
- 動態限速
- shared.dict 非隊列性質
- 如何添加自己的lua api
- 正確使用長鏈接
- 如何引用第三方resty庫
- 使用動態DNS來完成HTTP請求
- 緩存失效風暴
- Lua
- 下標從1開始
- 局部變量
- 判斷數組大小
- 非空判斷
- 正則表達式
- 不用標準庫
- 虛變量
- 函數在調用代碼前定義
- 抵制使用module()函數來定義Lua模塊
- 點號與冒號操作符的區別
- 測試
- 單元測試
- API測試
- 性能測試
- 持續集成
- 灰度發布
- web服務
- API的設計
- 數據合法性檢測
- 協議無痛升級
- 代碼規范
- 連接池
- c10k編程
- TIME_WAIT問題
- 與Docker使用的網絡瓶頸
- 火焰圖
- 什么時候使用
- 顯示的是什么
- 如何安裝火焰圖生成工具
- 如何定位問題