掃二維碼與項目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流
今天我們來介紹另一種場景:MySQL 客戶端給服務端發(fā)送一條 SQL 之后,服務端執(zhí)行 SQL 的過程中,客戶端沒有任何通知,就直接斷開了連接。

這種情況下,服務端會怎么辦?
本文內(nèi)容基于 MySQL 8.0.32 源碼,涉及存儲引擎為 InnoDB。
為了和前一篇文章介紹的場景區(qū)分開,我們用兩個虛構小故事把兩種場景放在一起作個對比。
張三(MySQL 客戶端)和李四(服務端)是好朋友,它送給了李四一個禮物(發(fā)送了一條 DML/DDL SQL)。
有一天,張三和李四鬧別扭,它后悔送禮物給李四了,于是它對李四說:把我送你的禮物還給我(??Ctrl + C 要求服務端中斷 SQL 執(zhí)行??)。
李四要先把張三送給它的禮物找出來,才能還給張三。
如果禮物還在(事務還沒有提交),李四就能把禮物還給張三(中斷執(zhí)行,回滾事務);如果禮物不在了(事務已經(jīng)提交了),也就沒法還了。
張三(MySQL 客戶端)和李四(服務端)是好朋友,它送給了李四一個禮物(發(fā)送了一條 DML/DDL SQL)。
有一天,李四因為一件事情把張三惹毛了。
張三心里很不爽,它要跟李四絕交(直接斷開了連接),但對它而言,送出去的禮物就是潑出去的水,它不想收回。
李四的性格大大咧咧,它不知道自己把張三惹毛了,還在美滋滋的欣賞張三送給它的禮物(執(zhí)行 SQL)。
等它回過頭來想找張三的時候,發(fā)現(xiàn)找不著了,它才回想起來,可能自己把張三惹毛了,朋友沒得做了。
此時,李四要怎么對待張三送給它的禮物呢?
接下來,我們跳出虛構,回歸現(xiàn)實,來捋一下場景二的流程。
這種場景只會出現(xiàn)在通過程序連接 MySQL 服務端,程序沒有關閉數(shù)據(jù)庫連接就執(zhí)行結束或者崩潰了
MySQL 客戶端發(fā)送一條 DML/DDL SQL 給服務端,服務端收到之后,就開始吭哧吭哧地執(zhí)行。
SQL 執(zhí)行完成之前,客戶端再沒有給服務端發(fā)送任何消息,就直接斷開連接了。
SQL 執(zhí)行過程中,服務端并不能感知到連接已經(jīng)斷開了,它還會一直賣力地執(zhí)行 SQL。
SQL 執(zhí)行完成之后,問題來了。
如果服務端執(zhí)行的是 DDL 語句,一條 SQL 執(zhí)行完成之后,會自動提交事務。
如果服務端執(zhí)行的是 DML? 語句,并且系統(tǒng)變量 auto_commit = on,一條 SQL 執(zhí)行完成之后,也會自動提交事務。
因為服務端不知道客戶端已經(jīng)斷開連接了,事務提交之后,它會把 SQL 執(zhí)行結果發(fā)送給客戶端。
把結果發(fā)送給客戶端,執(zhí)行的是 send() 系統(tǒng)調用,send() 有兩種行為:
行為一,如果 send() 把 SQL 執(zhí)行結果寫入 socket 緩沖區(qū),會返回寫入成功,此時,服務端還不會感知到客戶端已經(jīng)斷開連接。
send() 執(zhí)行成功之后,服務端認為這條 SQL 就告一段落了,會等待客戶端發(fā)送下一條命令。
由于客戶端已經(jīng)斷開連接,從當前連接讀取下一條命令時會出錯。
此時,服務端會感知到客戶端已經(jīng)斷開連接了。
行為二,如果 send() 直接把 SQL 執(zhí)行結果發(fā)送給客戶端,服務端就能馬上感知到客戶端已經(jīng)斷開連接了。
不管 send() 發(fā)生哪種行為,服務端都會感知到客戶端已經(jīng)斷開連接了,無非早一點晚一點而已。
感知到連接斷開之后,服務端會回滾事務。
但是,由于執(zhí)行 send() 之前,服務端已經(jīng)把事務提交了,這里回滾事務并不會生效。
那么,最終結果就是:服務端對于數(shù)據(jù)的修改會被持久化,永久生效。
如果服務端執(zhí)行的是 DML 語句,并且用 begin 或 start transaction 顯式開啟了事務,一條 SQL 執(zhí)行完成之后,不會自動提交事務,而是會等待客戶端發(fā)送下一條命令。
讀取下一條命令之前,服務端會執(zhí)行 send() 系統(tǒng)調用,把當前 SQL 的執(zhí)行結果發(fā)送給客戶端。
?行為一,如果 send() 把 SQL 執(zhí)行結果寫入 socket 緩沖區(qū),會返回寫入成功,此時,服務端還不會感知到客戶端已經(jīng)斷開連接。
send() 執(zhí)行成功之后,服務端認為這條 SQL 就告一段落了,會等待客戶端發(fā)送下一條命令。
由于客戶端已經(jīng)斷開連接,從當前連接讀取下一條命令時會出錯。
此時,服務端會感知到客戶端已經(jīng)斷開連接了。
行為二,如果 send() 直接把 SQL 執(zhí)行結果發(fā)送給客戶端,服務端就能馬上感知到客戶端已經(jīng)斷開連接了。
不管 send() 發(fā)生哪種行為,服務端都會感知到客戶端已經(jīng)斷開連接了,無非早一點晚一點而已。
感知到連接斷開之后,服務端會回滾事務?,由于執(zhí)行 send() 之前,并沒有提交過事務,這里回滾事務會生效。
那么,最終結果就是:服務端對于數(shù)據(jù)的修改會被回滾,相當于沒有執(zhí)行過 DML 操作。
前面展開介紹了 MySQL 客戶端不辭而別之后,服務端進行的一系列操作,總結起來就 3 條:
第 1 條:如果服務端執(zhí)行的是 DDL 語句,DDL 會執(zhí)行成功。
第 2 條:如果服務端執(zhí)行的是 DML? 語句,并且系統(tǒng)變量 auto_commit = on,DML 也會執(zhí)行成功。
第 3 條:如果服務端執(zhí)行的是 DML? 語句,并且用 begin、start transaction 顯式開啟了事務或者系統(tǒng)變量 auto_commit = off,事務會被回滾,DML 相當于沒有執(zhí)行。
前面介紹到 send() 有兩種行為:
這兩種行為由操作系統(tǒng)內(nèi)核決定,目前對于這個機制還沒有完全研究清楚,留待后續(xù)。
本文轉載自微信公眾號「一樹一溪」,可以通過以下二維碼關注。轉載本文請聯(lián)系一樹一溪公眾號。

我們在微信上24小時期待你的聲音
解答本文疑問/技術咨詢/運營咨詢/技術建議/互聯(lián)網(wǎng)交流