鍍金池/ 教程/ Java/ 不支持事務(wù)
定時(shí)任務(wù)
函數(shù)的參數(shù)
超時(shí)
一個(gè) openresty 內(nèi)存“泄漏”問題
獲取 uri 參數(shù)
局部變量
sleep
灰度發(fā)布
TIME_WAIT
代碼覆蓋率
連接池
CentOS 平臺安裝
稀疏數(shù)組
如何只啟動(dòng)一個(gè) timer 工作?
變量的共享范圍
break,return 關(guān)鍵字
Nginx
SQL 注入
如何引用第三方 resty 庫
不同階段共享變量
獲取請求 body
動(dòng)態(tài)生成的 lua-resty-redis 模塊方法
動(dòng)態(tài)加載證書和 OCSP stapling
repeat 控制結(jié)構(gòu)
編碼為 array 還是 object
Nginx 靜態(tài)文件服務(wù)
執(zhí)行階段概念
Lua 函數(shù)
日期時(shí)間函數(shù)
健康監(jiān)測
與其他 location 配合
for 控制結(jié)構(gòu)
函數(shù)定義
HTTPS 時(shí)代
點(diǎn)號與冒號操作符的區(qū)別
String 庫
文件操作
OpenResty 最佳實(shí)踐
<code>ngx.shared.DICT</code> 非隊(duì)列性質(zhì)
使用動(dòng)態(tài) DNS 來完成 HTTP 請求
代碼規(guī)范
什么是 JIT?
Windows 平臺安裝
正確的記錄日志
LuaNginxModule
不用標(biāo)準(zhǔn)庫
C10K 編程
控制結(jié)構(gòu)
請求中斷后的處理
Lua 環(huán)境搭建
Test::Nginx 能指定現(xiàn)成的 nginx.conf,而不是自動(dòng)生成一個(gè)嗎
Lua 基礎(chǔ)數(shù)據(jù)類型
動(dòng)態(tài)限速
PostgresNginxModule
簡單API Server框架
API 測試
location 匹配規(guī)則
虛變量
單元測試
防止 SQL 注入
select + set_keepalive 組合操作引起的數(shù)據(jù)讀寫錯(cuò)誤
阻塞操作
全動(dòng)態(tài)函數(shù)調(diào)用
Web 服務(wù)
典型應(yīng)用場景
Nginx 新手起步
TLS session resumption
輸出響應(yīng)體
調(diào)用代碼前先定義函數(shù)
module 是邪惡的
怎樣理解 cosocket
模塊
Socket 編程發(fā)展
如何對 Nginx Lua module 添加新 api
如何在后臺開啟輕量級線程完成定時(shí)任務(wù)?
如何定位問題
table 庫
json 解析的異常捕獲
如何安裝火焰圖生成工具
lua 中如何 continue
if 是邪惡的
為什么我們的域名不能被解析
抵制使用 module() 定義模塊
測試
body 在 location 中的傳遞
Lua 入門
子查詢
pipeline 壓縮請求數(shù)量
如何發(fā)起新 HTTP 請求
Lua 簡介
緩存失效風(fēng)暴
Ubuntu 平臺安裝
日志輸出
緩存
Lua 面向?qū)ο缶幊?/span>
Nginx 陷阱和常見錯(cuò)誤
Redis 接口的二次封裝(發(fā)布訂閱)
日志
訪問有授權(quán)驗(yàn)證的 Redis
正則表達(dá)式
lock
熱裝載代碼
調(diào)用 FFI 出現(xiàn) &quot;table overflow&quot;
數(shù)據(jù)合法性檢測
禁止某些終端訪問
控制結(jié)構(gòu) if-else
調(diào)試
與 Docker 使用的網(wǎng)絡(luò)瓶頸
PostgresNginxModule 模塊的調(diào)用方式
用 do-end 整理你的代碼
FFI
什么時(shí)候使用
簡介
環(huán)境搭建
Mac OS X 平臺安裝
火焰圖
負(fù)載均衡
while 型控制結(jié)構(gòu)
如何定位 openresty 崩潰 bug
使用 Nginx 內(nèi)置綁定變量
判斷數(shù)組大小
請求返回后繼續(xù)執(zhí)行
Redis 接口的二次封裝
KeepAlive
反向代理
協(xié)議無痛升級
數(shù)學(xué)庫
元表
Vanilla 介紹
HelloWorld
LuaCjsonLibrary
持續(xù)集成
代碼靜態(tài)分析
網(wǎng)上有大量對 Lua 調(diào)優(yōu)的推薦,我們應(yīng)該如何看待?
script 壓縮復(fù)雜請求
非空判斷
性能測試
函數(shù)返回值
API 的設(shè)計(jì)
kong 介紹
表達(dá)式
不支持事務(wù)
LuaRestyDNSLibrary 簡介

不支持事務(wù)

我們繼續(xù)上一章節(jié)的內(nèi)容,大家應(yīng)該記得我們 Lua 代碼中是如何完成 ngx_postgres 模塊調(diào)用的。我們把他簡單改造一下,讓他更接近真實(shí)代碼。

    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

    -- 轉(zhuǎn)賬操作,對ID=100的用戶加10,同時(shí)對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")

后面這部分有問題的代碼,在沒有并發(fā)的場景下使用,是不會(huì)有任何問題的。但是這段代碼在高并發(fā)應(yīng)用場景下,錯(cuò)誤百出。你會(huì)發(fā)現(xiàn)最后執(zhí)行結(jié)果完全摸不清楚。明明是個(gè)轉(zhuǎn)賬邏輯,一個(gè)收入,一直支出,最后卻發(fā)現(xiàn)總收入比支出要大。如果這個(gè)錯(cuò)誤發(fā)生在金融領(lǐng)域,那不知道要賠多少錢。

如果你能靠自己很快明白錯(cuò)誤的原因,那么恭喜你你對數(shù)據(jù)庫連接 Nginx 機(jī)理都是比較清楚的。如果你想不明白,那就聽我給你掰一掰這面的小知識。

數(shù)據(jù)庫的事物成功執(zhí)行,事物相關(guān)的所有操作是必須執(zhí)行在一條連接上的。SQL 的執(zhí)行情況類似這樣:

連接:`BEGIN` -> `SQL(UPDATE、DELETE... ...)` -> `COMMIT`。

但如果你創(chuàng)建了兩條連接,每條連接提交的 SQL 語句是下面這樣:

連接1:`BEGIN` -> `SQL(UPDATE、DELETE... ...)`
連接2:`COMMIT`

這時(shí)就會(huì)出現(xiàn)連接 1 的內(nèi)容沒有被提交,行鎖產(chǎn)生。連接 2 提交了一個(gè)空的 COMMIT。

說到這里你可能開始鄙視我了,誰瘋了非要?jiǎng)?chuàng)建兩條連接來這么用 SQL 啊。又麻煩,又不好看,貌似從來沒聽說過還有人在一次請求中創(chuàng)建多個(gè)數(shù)據(jù)庫連接,簡直就是非人類嘛。

或許你不會(huì)主動(dòng)、顯示的創(chuàng)建多個(gè)連接,但是剛剛的示例代碼,高并發(fā)下這個(gè)事物的每個(gè) SQL 語句都可能落在不同的連接上。為什么呢?這是因?yàn)橥ㄟ^ ngx.location.capture 跳轉(zhuǎn)到 /postgres 小節(jié)后,Nginx 每次都會(huì)從連接池中挑選一條空閑連接,而當(dāng)時(shí)哪條連接是空閑的,完全沒法預(yù)估。所以上面的第二個(gè)例子,就這么靜悄悄的發(fā)生了。如果你不了解 Nginx 的機(jī)理,那么他肯定會(huì)一直困擾你。為什么一會(huì)兒好,一會(huì)兒不好。

同樣的道理,我們推理到 DrizzleNginxModule、RedisNginxModule、Redis2NginxModule,他們都是無法做到在兩次連續(xù)請求落到同一個(gè)連接上的。

由于這個(gè) Bug 藏得比較深,并且不太好講解,所以我覺得生產(chǎn)中最好用 lua-resty-* 這類的庫,更符合標(biāo)準(zhǔn)調(diào)用習(xí)慣,直接可以繞過這些坑。不要為了一點(diǎn)點(diǎn)的性能,犧牲了更大的蛋糕??吹靡姷?,看不見的,都要了解用用,最后再做決定,肯定不吃虧。