一個(gè)驗(yàn)證事務(wù)效果的樣例代碼)
很多開(kāi)發(fā)者學(xué)習(xí)MySQL事務(wù)的時(shí)候只停留在理論層面不知道事務(wù)真正生效是什么樣子。本文提供兩套對(duì)照可運(yùn)行代碼無(wú)事務(wù)產(chǎn)生臟數(shù)據(jù)、開(kāi)啟事務(wù)異常自動(dòng)回滾。直接運(yùn)行代碼觀察數(shù)據(jù)庫(kù)數(shù)據(jù)變化直觀驗(yàn)證事務(wù)效果。環(huán)境準(zhǔn)備安裝依賴(lài)pipinstallpymysql測(cè)試表SQL必須使用InnoDB引擎MyISAM不支持事務(wù)。CREATETABLEaccount(idINTPRIMARYKEYAUTO_INCREMENT,usernameVARCHAR(32)NOTNULL,balanceDECIMAL(12,2)NOTNULLDEFAULT0)ENGINEInnoDBDEFAULTCHARSETutf8mb4;-- 初始化測(cè)試數(shù)據(jù)張三、李四各1000元INSERTINTOaccount(username,balance)VALUES(張三,1000.00),(李四,1000.00);業(yè)務(wù)場(chǎng)景張三轉(zhuǎn)賬200元給李四。流程張三余額扣減200模擬程序拋出異常李四余額增加200案例一不使用事務(wù)自動(dòng)提交產(chǎn)生臟數(shù)據(jù)pymysql默認(rèn)autocommitTrue每執(zhí)行一條SQL就直接寫(xiě)入數(shù)據(jù)庫(kù)。當(dāng)中間代碼拋出異常后面SQL無(wú)法執(zhí)行就會(huì)出現(xiàn)一部分成功、一部分失敗的數(shù)據(jù)錯(cuò)亂。importpymysqldefdemo_without_transaction():# 默認(rèn) autocommitTrueconnpymysql.connect(host127.0.0.1,port3306,userroot,password你的密碼,databasetest_db,charsetutf8mb4)cursorconn.cursor()# 1.張三扣200這條SQL立刻生效落庫(kù)cursor.execute(UPDATE account SET balance balance - %s WHERE username%s,(200,張三))# 模擬業(yè)務(wù)異常、接口報(bào)錯(cuò)、程序崩潰print(模擬程序發(fā)生異常)1/0# 2.李四加錢(qián)這行代碼永遠(yuǎn)不會(huì)執(zhí)行cursor.execute(UPDATE account SET balance balance %s WHERE username%s,(200,李四))cursor.close()conn.close()if__name____main__:demo_without_transaction()運(yùn)行現(xiàn)象程序拋出ZeroDivisionError異常終止查詢(xún)數(shù)據(jù)庫(kù)張三余額變成800李四仍然1000出現(xiàn)臟數(shù)據(jù)錢(qián)憑空消失業(yè)務(wù)邏輯被破壞??復(fù)現(xiàn)完成后記得重置表數(shù)據(jù)方便測(cè)試下一個(gè)案例。UPDATEaccountSETbalance1000.00;案例二開(kāi)啟事務(wù)異常自動(dòng)回滾關(guān)鍵點(diǎn)設(shè)置conn.autocommit False關(guān)閉自動(dòng)提交所有修改操作先在事務(wù)緩沖區(qū)不會(huì)真正寫(xiě)入數(shù)據(jù)庫(kù)正常執(zhí)行完畢調(diào)用conn.commit()持久化數(shù)據(jù)捕獲異常調(diào)用conn.rollback()撤銷(xiāo)全部修改importpymysqldefdemo_with_transaction():connpymysql.connect(host127.0.0.1,port3306,userroot,password你的密碼,databasetest_db,charsetutf8mb4)# 關(guān)閉自動(dòng)提交開(kāi)啟事務(wù)模式conn.autocommitFalsecursorconn.cursor()try:# 第一步張三扣款cursor.execute(UPDATE account SET balance balance - %s WHERE username%s,(200,張三))# 模擬異常觸發(fā)回滾邏輯print(模擬程序發(fā)生異常)1/0# 第二步李四收款cursor.execute(UPDATE account SET balance balance %s WHERE username%s,(200,李四))# 全部執(zhí)行成功提交事務(wù)數(shù)據(jù)真正寫(xiě)入數(shù)據(jù)庫(kù)conn.commit()print(?事務(wù)提交成功轉(zhuǎn)賬完成)exceptExceptionase:# 出現(xiàn)任何異?;貪L本次事務(wù)所有變更c(diǎn)onn.rollback()print(f?捕獲異常事務(wù)已全部回滾異常信息{e})finally:cursor.close()conn.close()if__name____main__:demo_with_transaction()運(yùn)行現(xiàn)象異常分支程序拋出除零異常進(jìn)入except代碼塊執(zhí)行rollback()查詢(xún)數(shù)據(jù)庫(kù)張三、李四余額依舊都是1000數(shù)據(jù)完全沒(méi)有變化所有update操作全部撤銷(xiāo)不會(huì)產(chǎn)生臟數(shù)據(jù)測(cè)試正常提交場(chǎng)景把代碼中的1 / 0注釋掉再次運(yùn)行。不會(huì)觸發(fā)異常執(zhí)行commit()數(shù)據(jù)庫(kù)張三800李四1200轉(zhuǎn)賬業(yè)務(wù)正常完成。案例三演示部分回滾SAVEPOINT保存點(diǎn)MySQL支持保存點(diǎn)可以實(shí)現(xiàn)事務(wù)內(nèi)部局部回滾不需要全部回滾。importpymysqldefdemo_savepoint():connpymysql.connect(host127.0.0.1,port3306,userroot,password你的密碼,databasetest_db,autocommitFalse,charsetutf8mb4)curconn.cursor()try:cur.execute(UPDATE account SET balancebalance-100 WHERE username張三)# 設(shè)置保存點(diǎn)sp1cur.execute(SAVEPOINT sp1)cur.execute(UPDATE account SET balancebalance100 WHERE username李四)# 回滾到保存點(diǎn)撤銷(xiāo)上面李四加錢(qián)操作張三扣錢(qián)保留cur.execute(ROLLBACK TO SAVEPOINT sp1)conn.commit()print(保存點(diǎn)演示完成)exceptExceptionase:conn.rollback()print(f異?;貪L{e})finally:cur.close()conn.close()對(duì)比總結(jié)表場(chǎng)景無(wú)事務(wù) autocommitTrue開(kāi)啟事務(wù) autocommitFalse程序中途報(bào)錯(cuò)部分SQL生效生成臟數(shù)據(jù)全部操作回滾數(shù)據(jù)不變程序正常結(jié)束逐條SQL立即生效commit之后統(tǒng)一生效開(kāi)發(fā)避坑要點(diǎn)autocommitFalse是開(kāi)啟事務(wù)的前提忘記關(guān)閉自動(dòng)提交commit/rollback完全無(wú)效。同一個(gè)事務(wù)內(nèi)所有SQL必須使用同一個(gè)connection對(duì)象更換連接代表全新事務(wù)。異常分支必須手動(dòng)執(zhí)行rollback()否則未提交事務(wù)會(huì)掛起占用數(shù)據(jù)庫(kù)鎖資源。表引擎必須是InnoDBMyISAM不支持事務(wù)。盡量縮小事務(wù)粒度避免長(zhǎng)事務(wù)長(zhǎng)事務(wù)會(huì)造成鎖等待、數(shù)據(jù)庫(kù)性能下降。SELECT查詢(xún)語(yǔ)句不會(huì)修改數(shù)據(jù)不會(huì)產(chǎn)生事務(wù)變更SELECT ... FOR UPDATE會(huì)增加行鎖。業(yè)務(wù)模板訂單、庫(kù)存、轉(zhuǎn)賬等多寫(xiě)業(yè)務(wù)直接套用這套模板conn.autocommitFalsetry:# 多條寫(xiě)sqlconn.commit()exceptException:conn.rollback()finally:conn.close()通過(guò)以上樣例代碼就可以直觀驗(yàn)證MySQL事務(wù)原子性效果一組操作要么全部成功要么全部撤銷(xiāo)。