我們繼續(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)的性能,犧牲了更大的蛋糕??吹靡姷?,看不見的,都要了解用用,最后再做決定,肯定不吃虧。