国产av一二三区|日本不卡动作网站|黄色天天久久影片|99草成人免费在线视频|AV三级片成人电影在线|成年人aV不卡免费播放|日韩无码成人一级片视频|人人看人人玩开心色AV|人妻系列在线观看|亚洲av无码一区二区三区在线播放

網(wǎng)易首頁 > 網(wǎng)易號 > 正文 申請入駐

一行UPDATE語句讓500萬蒸發(fā),這個程序員用7張表重建了銀行

0
分享至


2023年某支付平臺故障,用戶余額憑空多出一個零。工程師排查了72小時,最終發(fā)現(xiàn)是一行UPDATE語句在并發(fā)時撞上了 race condition(競態(tài)條件)。錢從哪來?到哪去?沒人說得清。

這種事故在金融科技領(lǐng)域不算新聞。單字段存余額的設(shè)計,就像把現(xiàn)金鎖在抽屜里卻不記賬——抽屜一亂,全盤皆輸。

Paul Babatuyi 在 GitHub 開源的 double-entry-bank 項目,用 Go 和 PostgreSQL 演示了另一種思路:雙分錄記賬。每筆交易生成兩條記錄,一借一貸,相互勾稽。沒有 UPDATE,只有 INSERT;沒有刪除,只有追加。賬本變成只讀的歷史流,任何異常都能被追溯。

為什么單字段余額是定時炸彈

開發(fā)者常犯的錯,是把"余額"當(dāng)成一個可以隨便改的數(shù)字。UPDATE accounts SET balance = balance - 100 看起來無害,實則埋了三顆雷。

第一顆是原子性。 兩條并發(fā)請求同時讀取余額1000,各自減去100,最終寫入900。實際應(yīng)該扣200,系統(tǒng)卻只扣了100。這種"丟失更新"在流量高峰時幾乎必然發(fā)生。

第二顆是可審計性。 余額字段被覆蓋后,舊值永久消失。用戶投訴"錢少了",你只能看到當(dāng)前數(shù)字,無法還原變化路徑。監(jiān)管合規(guī)?更是無從談起。

第三顆是人為風(fēng)險。 擁有 UPDATE 權(quán)限的工程師,理論上可以靜默修改任意賬戶。沒有日志交叉驗證,內(nèi)部舞弊難以被發(fā)現(xiàn)。

雙分錄記賬的解法很古老——1494年盧卡·帕喬利在《算術(shù)、幾何、比及比例概要》中系統(tǒng)闡述的復(fù)式記賬,至今仍是現(xiàn)代金融的基石。每一筆資金流動必須同時記錄來源和去向,總額恒等。

7張表如何鎖住每一分錢

Paul 的數(shù)據(jù)庫設(shè)計沒有魔法,只是把"余額"這個概念拆解成了不可變的事件流。

核心表只有三張:accounts(賬戶定義)、entries(分錄明細)、transfers(交易主檔)。accounts 記錄賬戶類型和幣種,余額不存這里。entries 是真正的賬本,每條記錄包含賬戶ID、金額、借貸方向,以及指向 transfers 的外鍵。transfers 則保存交易元數(shù)據(jù):發(fā)起方、接收方、時間戳、外部流水號。


查詢余額時,不再是 SELECT balance FROM accounts,而是 SUM(entries.amount) WHERE account_id = X。實時計算聽起來費性能?PostgreSQL 的索引和物化視圖能搞定,或者像 Stripe 那樣異步匯總到緩存——但緩存只是只讀副本,真相永遠在 entries 表里。

另外四張表處理支撐功能:users、sessions、verify_emails 管身份,還有一張參數(shù)表存配置。業(yè)務(wù)表與支撐表隔離,權(quán)限粒度可以收得很細。

sqlc 把 SQL 變成類型安全的 Go 代碼

手寫數(shù)據(jù)庫操作容易出錯。字段名拼錯、類型對不上、NULL 處理遺漏——這些 bug 在編譯期發(fā)現(xiàn)不了,上線后才炸。

sqlc 的解決思路是反向生成:你先寫 SQL 查詢,它自動生成對應(yīng)的 Go 結(jié)構(gòu)和接口。查詢文件放在 postgres/queries/ 目錄,sqlc.yaml 配置好連接信息,執(zhí)行 sqlc generate 就能得到 db.go、models.go、querier.go 三個文件。

好處是 SQL 和 Go 的類型系統(tǒng)打通。你在查詢里寫 SELECT id, owner, balance FROM accounts,生成的代碼就返回包含這三個字段的結(jié)構(gòu)體。改表結(jié)構(gòu)?改 SQL 文件重新生成,編譯錯誤會精準(zhǔn)定位到所有受影響的地方。

Paul 的遷移文件用 golang-migrate 管理,版本號順序執(zhí)行,回滾也有對應(yīng)腳本。這套組合讓數(shù)據(jù)庫變更可 review、可回滾、可追蹤——和雙分錄記賬的"不可變"哲學(xué)一脈相承。

Store 層:事務(wù)重試與死鎖防御

雙分錄記賬的寫入比單字段復(fù)雜得多。一筆轉(zhuǎn)賬要同時插入 transfers 記錄和兩條 entries 記錄,三者必須在同一個數(shù)據(jù)庫事務(wù)里。

但事務(wù)帶來新問題:并發(fā)寫入相同賬戶時,PostgreSQL 的行鎖可能引發(fā)死鎖。Paul 的解法是在 Store 層封裝 ExecTx 函數(shù),自動檢測 pgx.ErrTxCommitRollback 錯誤,用指數(shù)退避算法重試。重試邏輯寫在代碼里,而不是拋給調(diào)用方處理。

更細的設(shè)計是隔離級別。默認的 Read Committed 在多數(shù)場景夠用,但涉及余額校驗時可能需要 Repeatable Read。Paul 把隔離級別作為參數(shù)暴露,讓業(yè)務(wù)層按需選擇。

測試覆蓋了極端場景:并發(fā)轉(zhuǎn)賬、部分失敗、網(wǎng)絡(luò)中斷后的狀態(tài)一致性。用的不是 mock,而是 testcontainers 啟動真實 PostgreSQL 容器,確保測試通過即生產(chǎn)可用。


Service 層:業(yè)務(wù)規(guī)則與賬本操作解耦

Store 層只管"怎么存",Service 層決定"存什么"。這種分層讓單元測試可以 mock 數(shù)據(jù)庫,也讓業(yè)務(wù)邏輯不被 SQL 細節(jié)污染。

轉(zhuǎn)賬流程在 Service 層被拆解為:校驗身份→檢查余額充足→創(chuàng)建 transfer 記錄→生成借貸 entries→提交事務(wù)。任何一步失敗,整個事務(wù)回滾,不會出現(xiàn)"錢扣了但沒到賬"的中間狀態(tài)。

余額檢查的實現(xiàn)值得一提。因為 entries 表只增不改,"當(dāng)前余額"是聚合計算的結(jié)果。Paul 在代碼里用 SELECT FOR UPDATE 鎖定相關(guān)賬戶的 entries 插入權(quán)限,同時計算 SUM(amount) 得到實時余額。這比 SELECT FOR UPDATE 鎖整張表更精細,并發(fā)性能更好。

貨幣處理也埋了細節(jié)。金額用 bigint 存最小單位(分),避免浮點誤差。幣種代碼硬校驗,防止把 USD 和 EUR 混算。這些約束在數(shù)據(jù)庫層和代碼層各設(shè)一道,雙保險。

從演示項目到生產(chǎn)系統(tǒng)

golangbank.app 的在線演示很完整:注冊、登錄、轉(zhuǎn)賬、查流水、導(dǎo)出報表。Swagger 文檔自動生成,前端用 Next.js 實現(xiàn),前后端分離部署。

但生產(chǎn)環(huán)境還要補很多課。審計日志需要獨立存儲,防篡改;敏感操作要雙因素認證;大額轉(zhuǎn)賬延遲結(jié)算,預(yù)留風(fēng)控窗口。Paul 的項目是骨架,血肉需要按業(yè)務(wù)填充。

一個值得參考的演進方向是事件溯源(Event Sourcing)。entries 表本質(zhì)上已經(jīng)是事件流,如果加上 Kafka 或 Pulsar 做異步分發(fā),就能支撐更復(fù)雜的業(yè)務(wù):實時風(fēng)控、多幣種清算、甚至跨行對賬。

另一個方向是分片。entries 表隨時間膨脹,單機 PostgreSQL 遲早觸頂。按賬戶ID哈希分片,或者按時間冷熱分離,都是常見策略。雙分錄記賬的優(yōu)勢在于:分片不影響一致性模型,因為每筆交易的借貸雙方可以落在不同分片,只要保證 transfers 記錄的完整性。

回到開頭的那行 UPDATE。在 Paul 的系統(tǒng)里,這句話永遠不會出現(xiàn)。余額不是被修改的,而是被計算出來的;歷史不是被覆蓋的,而是被追加的。這種設(shè)計多寫了代碼,多占了存儲,但換來的是可審計、可回滾、可信任。

當(dāng)你的系統(tǒng)開始處理真金白銀,"簡單"往往是最貴的選擇。你愿意為這行消失的 UPDATE,多寫多少行代碼?

特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務(wù)。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相關(guān)推薦
熱點推薦
帶娃出國的中產(chǎn)后悔了:想回來,但回國失敗

帶娃出國的中產(chǎn)后悔了:想回來,但回國失敗

視覺志
2026-03-24 10:36:13
回顧“91女神”琪琪:五官出眾,卻因天真讓自己“受傷”

回顧“91女神”琪琪:五官出眾,卻因天真讓自己“受傷”

就一點
2025-11-22 10:36:39
舊手機回收價狂漲五六倍!回收商:開不了機的照樣高價收

舊手機回收價狂漲五六倍!回收商:開不了機的照樣高價收

快科技
2026-03-25 14:53:24
東契奇43分創(chuàng)紀(jì)錄,湖人逆轉(zhuǎn)勝步行者穩(wěn)西部第三

東契奇43分創(chuàng)紀(jì)錄,湖人逆轉(zhuǎn)勝步行者穩(wěn)西部第三

李絙在北漂
2026-03-26 13:20:36
51歲孔令輝:為生活奔波,與劉國梁9年未見面,一個電話讓他落淚

51歲孔令輝:為生活奔波,與劉國梁9年未見面,一個電話讓他落淚

夢史
2026-03-25 11:33:08
王勵勤動真格了!倫敦世乒賽陣容有變,陳夢調(diào)整,2人被冷落

王勵勤動真格了!倫敦世乒賽陣容有變,陳夢調(diào)整,2人被冷落

不似少年游
2026-03-25 17:03:59
你知道我在床上有多厲害嗎?

你知道我在床上有多厲害嗎?

果粉之家
2026-03-20 12:35:16
從歐爾班行為,看北約當(dāng)年拒絕俄羅斯的遠見

從歐爾班行為,看北約當(dāng)年拒絕俄羅斯的遠見

民間胡扯老哥
2026-03-23 18:53:38
破防!小縣城殯儀館大屏流出,中年人扎堆離世,網(wǎng)友:還爭什么?

破防!小縣城殯儀館大屏流出,中年人扎堆離世,網(wǎng)友:還爭什么?

川渝視覺
2026-03-23 19:26:44
喪夫僅5個月,49歲翁帆突傳“喜訊”高調(diào)露面,狀態(tài)好到出人意料

喪夫僅5個月,49歲翁帆突傳“喜訊”高調(diào)露面,狀態(tài)好到出人意料

冷紫葉
2026-03-24 19:12:36
勇士逆轉(zhuǎn)橫掃送籃網(wǎng)9連敗 桑托斯31分大爆發(fā)波杰姆斯基22分

勇士逆轉(zhuǎn)橫掃送籃網(wǎng)9連敗 桑托斯31分大爆發(fā)波杰姆斯基22分

醉臥浮生
2026-03-26 12:45:06
第六險來了!一文讀懂“長護險”

第六險來了!一文讀懂“長護險”

極目新聞
2026-03-26 07:21:00
張雪峰猝死后,盧克文也不敢跑步了,曾每年跑1千公里,身體垮了

張雪峰猝死后,盧克文也不敢跑步了,曾每年跑1千公里,身體垮了

水晶的視界
2026-03-26 09:28:29
有沒有人敢爆自己的瓜?網(wǎng)友:確定玩這么大嗎?

有沒有人敢爆自己的瓜?網(wǎng)友:確定玩這么大嗎?

夜深愛雜談
2026-02-18 20:55:58
四川高縣村支書暴打殘疾村婦致輕傷 法院判決免于刑事處罰引爭議

四川高縣村支書暴打殘疾村婦致輕傷 法院判決免于刑事處罰引爭議

律法刑道
2026-03-26 10:55:03
2.4億成全你!詹姆斯降薪2000萬?雙詹合體咋辦!

2.4億成全你!詹姆斯降薪2000萬?雙詹合體咋辦!

柚子說球
2026-03-25 11:44:10
薩巴倫卡贏鄭欽文后吃100美元漢堡 頂奢!魚子醬+金箔碎她說超好吃

薩巴倫卡贏鄭欽文后吃100美元漢堡 頂奢!魚子醬+金箔碎她說超好吃

勁爆體壇
2026-03-26 08:10:23
4000噸稀土被轉(zhuǎn)運美國?大陸停供臺灣稀土!臺學(xué)者:不如直接統(tǒng)一

4000噸稀土被轉(zhuǎn)運美國?大陸停供臺灣稀土!臺學(xué)者:不如直接統(tǒng)一

小舟談歷史
2026-03-19 17:27:44
RMC記者:姆巴佩在發(fā)布會上說謊了,我1000%確認他被誤診了

RMC記者:姆巴佩在發(fā)布會上說謊了,我1000%確認他被誤診了

懂球帝
2026-03-26 06:43:05
伊拉克民兵無人機出擊!炸了美軍的雷達,又擊中黑鷹直升機

伊拉克民兵無人機出擊!炸了美軍的雷達,又擊中黑鷹直升機

戰(zhàn)風(fēng)
2026-03-25 11:44:25
2026-03-26 13:55:00
像素與芯片
像素與芯片
有態(tài)度網(wǎng)友ytd
424文章數(shù) 2關(guān)注度
往期回顧 全部

科技要聞

Meta高管狂分百億期權(quán),700名員工卻下崗

頭條要聞

伊朗議長和外長暫被移出美以清除名單 時限4到5天

頭條要聞

伊朗議長和外長暫被移出美以清除名單 時限4到5天

體育要聞

35歲替補門將,憑什么入選英格蘭隊?

娛樂要聞

張雪峰家人首發(fā)聲 不設(shè)追思會喪事從簡

財經(jīng)要聞

黃仁勛:芯片公司的時代已經(jīng)結(jié)束了

汽車要聞

一汽奧迪A6L e-tron開啟預(yù)售 CLTC最大續(xù)航815km

態(tài)度原創(chuàng)

家居
親子
教育
時尚
軍事航空

家居要聞

傍海而居 靜觀蝴蝶海

親子要聞

躺平的孩子意外覺醒了,在父母學(xué)會當(dāng)“烏龜”!

教育要聞

2027屆注意:暑期實習(xí)=秋招通行證,錯過等一年

2026年了,最好看的還是“這件針織”!

軍事要聞

擔(dān)心特朗普突然停戰(zhàn) 以總理下令48小時盡力摧毀伊設(shè)施

無障礙瀏覽 進入關(guān)懷版