天天看點

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

本節書摘來自異步社群出版社《mysql排錯指南》一書中的第1章,第1.3節,作者:【美】sveta smirnova(斯維特 斯米爾諾娃),更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

如果select查詢傳回了非預期的結果集,這并不總是意味着查詢語句本身有錯誤,也有可能是因為你以為已經進行了插入、更新或者删除等操作,而事實上它們并未生效。

在你調查這種可能之前,你應該先完全仔細檢查前一節讨論的select語句編寫錯誤的問題。在select語句編寫正确并且能夠傳回你想要的值的情況下,現在我開始調查由資料本身的問題導緻錯誤的可能性。為了确認問題是由資料本身而非select語句産生的,我嘗試精簡語句,使其變成某個獨立表的簡單查詢。如果是小表,那麼移除所有的where條件和group by語句,然後通過“野蠻”的select from table - name檢查所有的資料。對于大表來說,用where條件來篩選出你想要的值是明智的選擇。如果你僅關心查詢結果集的條數是否和預期的一緻,也可以考慮用count()來顯示條數。

一旦你确定select查詢工作正常,那就意味着是資料不一緻産生的問題,你就需要定位哪裡出了問題。有很多可能的原因:使用了錯誤的備份、錯誤的update語句,或者從節點與主節點之間同步異常(這裡先僅列出這些最常見的可能)。在這一節中,我們會看到一些關于delete或update操作在随後的select查詢中沒有生效的示例。下一節會介紹一些令人困惑的情況,在這些情況中,問題會在被觸發很久後才出現,當然我們也會告訴你如何反向定位這類錯誤。本章并不涉及事務中的問題,這類問題将在第2章中讨論。這裡展示的場景都是基于資料庫中的資料已經穩定的前提,也就是說,所有使用到的事務都已經完成了。我将繼續使用從現實場景中簡化過的示例。

我們從可能的最佳情形開始,即錯誤發生後立即提示資料不一緻的問題。我們将使用下面的初始資料集:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

在應用程式中,臨時表包含從主日志表中查詢出來的部分結果集。這是一個儲存日常常用資料經常使用的技術手段,當你隻需要用到主表中的一小部分資料并且使用者不想改變主表中的資料或者鎖定主表的時候,可以使用臨時表。

是以在這個示例中,當使用完結果集後,使用者想要同時删除兩個表中的相應行。通常人們很難想象用一個查詢語句去做多件事情。不過現實可以與你的設想不同,并且你還會得到非預期的結果或負面影響:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

如果使用者注意觀察輸出的delete語句的相應結果,就會立即發現出了問題。delete操作沒有影響到任何行意味着它什麼都沒做。然而,一條語句的輸出通常不是這麼顯而易見,有時候它并不可見,因為sql語句是在程式或者腳本内部執行的,并且沒有人會去監控執行結果。通常情況下,你應該始終檢查語句執行的傳回資訊,進而了解有多少行資料受影響且它們的值是否與你預期的一緻。在應用程式中,你必須明确檢查資訊功能。

繼續下面的讨論。如果你立即執行select查詢,你可能會很驚訝,以為查詢語句出現了錯誤或者查詢緩存沒有清除:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

如果把select語句改為查詢行的數量,就可以确認這不是緩存或者其他相關的問題。這個小例子也告訴我們可以通過對同一張表進行不同的查詢方式來确認資料的一緻性:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

這裡count(*)仍然傳回一個正數,這表明表是非空的。細心的使用者應該已經注意到,delete操作實際上沒有删除任何行。為了找出原因,我們可以将delete語句改為相應的select語句。這樣做可以告訴我們哪些行滿足了删除條件。

盡管這個簡單的示例中沒有where語句,但是這個技巧對于包含where語句的删除和更新操作同樣有效。select語句傳回的行即為delete操作将要删除的行或者update操作将要更新的行:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

與之前的結果一緻,這裡也傳回空集合。這就是為什麼沒有删除任何行!然而,現在仍不清楚産生這個現象的具體原因,但是既然我們有一個select查詢,就可以利用第一節提到的相關技術。在這個場景中,最佳選擇就是用explain指令執行select語句然後分析輸出結果:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

輸出中最後的資訊表明查詢語句被修飾成了内部連接配接(inner join),該内部連接配接僅當另一張表也有滿足條件的行時才會同時傳回兩張表的行。對于t1表中的每一行,在t2表中應至少有一行的值比對。在這個示例中,因為t2表是空的,自然連接配接操作傳回空集合。

我們剛剛學習了另一個有助于找出update或delete語句錯誤原因的重要技巧:把語句轉換成具有相同join和where條件的select語句。針對select查詢,可以使用explain extended[1]指令去擷取實際的執行計劃,同時也可以避免直接操作結果集帶來的危險或者修改了錯誤的行。

這裡有一個的使用update的更複雜示例。我們仍使用items表:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

description和additional字段是text類型的。在這個示例中,我們将使用一個錯誤的語句,該語句想要把表中的中null值替換成更有語義的文本(一個替換成“no description”,另一個替換成“no additional comments”):

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

該語句會更新一些資料(“影響到3行”),讓我們檢查一下現在表中資料是否合理:

 

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

正如我們所見,有3行記錄的description字段的值被修改,不過值是0而不是我們預期的“no description”。并且,additional字段的值根本沒有改變。為了定位該問題發生的原因,我們應該檢查警告。注意伺服器傳回的這些語句,我們看到有共3個警告:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

這條消息看起來很奇怪。為什麼上述語句執行後,這裡會報告關于double的警告,而description和additional字段的類型都是text的。

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

我們還想知道為什麼additional字段完全沒有變化,并且我們也沒有得到任何警告。

我們把該語句拆分成小段,然後分别檢查每段都做了什麼:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

這是update語句慣用的開頭,沒有什麼問題:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

該段使用set語句。我們來檢查一下它實際做了什麼。and關鍵字在這裡究竟意味什麼?我們在語句中加上圓括号來突出一下運算符号優先級:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

是以,實際上這個語句計算了下清單達式:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

然後将值賦給description字段。計算等式會産生一個布爾類型的結果,表示為longlong類型的值。為了證明這點,以--column-type-info選項打開mysql指令行用戶端,然後再次運作select查詢:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

我們可以清楚地看到表達式的結果是0,這個值随後被插入了description字段。并且因為我們對additional字段的更新已被這個奇怪的表達式所覆寫了,是以沒有值插入該字段中,也就看不到伺服器端給出任何關于該字段的資訊。

現在可以修改上述語句中的邏輯錯誤了:

《MySQL排錯指南》——1.3 當錯誤可能由之前的更新引起時

如果需要你也可以檢查where語句,不過在這個示例裡它沒有錯誤。

這個示例表明傳回值和查詢執行資訊的重要性。我們來進一步讨論它們。

繼續閱讀