論述基礎與限制

本卡由一次量測結案抽出。待驗項是「唯一索引會不會讓最佳化器改寫存在性子查詢的計畫」,量測在 SQLite 3.51.0 上比對同一段 EXISTS 查詢在非唯一索引與唯一索引下的計畫。第一次比對用 EXPLAIN QUERY PLAN,兩邊印出逐字相同的一行 SEARCH y USING INDEX ix (recordDate=?),據此在回覆裡宣告兩個計畫相同。改用 EXPLAIN 看位元碼之後結論反轉:非唯一索引三十二行、內層 Next 兩個,唯一索引三十一行、內層 Next 一個——唯一時引擎知道那次查找至多回一列,整圈內層迴圈省掉了。

個案一則,無對照組。 量測由併行的另一個 session 完成並主動回報成方法層候選,本卡只做歸納。實例可複現:同一個引擎、同一組五千列資料、兩個檢視層各印一次。

核心原則

比對工具的輸出有它自己的粒度,「兩邊相同」的判定只在那個粒度上成立。 粗粒度的檢查回報零差異,與細粒度的檢查回報零差異,寫在報告上是同一句話——而前者只證明了那個粒度看不到差異,沒有證明差異不存在。

這是檢查規則的作用域要顯式列舉那個家族的第三個成員。偵測器編碼了幾個從不被審視的參數:作用域(看哪些檔)、視窗(往哪裡看)、粒度(看到多細)。那張卡處理前兩個,本卡處理第三個。三者的共同點是參數寫下的當時是個假設、寫下之後被當成定義;差別在失效方向——作用域缺漏與粒度不足都給假陰性,視窗錯誤給假陽性。

為什麼零差異特別會被當成結論

「相同」是一個不產出任何後續動作的結論。 差異被找到時會引出下一步——解釋它、量它、寫進哪一篇;零差異的下一步是結案。所以粒度的問題只在零差異這一側會被放過:有差異時人會去看那個差異長什麼樣,順帶就用了更細的工具;沒差異時沒有東西可看,第一層的輸出就是最後一層。

粗粒度工具通常是預設入口。 EXPLAIN QUERY PLAN 是 SQLite 文件教人讀計畫的第一個指令,三個節點一眼看完;位元碼三十行、要對照 opcode 表。第一層省力且多數時候夠用,於是它同時也是最後一層。這與已經記錄過的失效仍然重演裡「第一次成功恰好繞過失效路徑」是同一種形態:粗層在別的比對上給過正確答案,正是這一次不設防的理由。

這條與逐格驗證放過導出句不同。 #318 的缺口是層級——每一格對了、格子之上的那句話沒人驗;本卡的缺口是解析度——同一層看了,看的工具把差異壓掉了。前者要往上多驗一層,後者要往下多看一層。

修法

零差異的回報要附它的粒度。 「計畫相同」改寫成「在 EXPLAIN QUERY PLAN 這一層相同」,讀的人才知道這個結論的射程。附不出粒度的零差異不進結論。

手上有更細的一層就跑一次。 判斷標準是一句話:這個工具的輸出會不會把我要找的那種差異壓掉。答不出來就往下跑一層;兩層都零差異才算零差異。這一步的成本通常是一條指令——那次是把 EXPLAIN QUERY PLAN 換成 EXPLAIN。

正對照要放在細的那一層。 要證明「這個粒度看得到我要找的差異」,拿一個已知有差異的比對餵給同一個工具,看它印不印得出來。那次的正對照現成:位元碼層看得到內層迴圈少一圈,計畫層看不到,所以計畫層對這一類差異的正對照本身就失敗——它從一開始就不是能回答這個問題的工具。#317 把正對照放進掃描的輸出,本卡把它放進粒度的選擇。

時間量測不能代替結構量測。 那次同組資料七次計時取最小都是同一個值,計畫的差異在這個資料量下不出時間差。時間是更粗的一層:它把整條計畫壓成一個數字,看不到差異是預期中的。結構層有差異而時間層沒有,兩者都要記,結論寫成「計畫不同、這個規模量不出時間差」。

跟其他原則的關係

判讀徵兆

  • 比對回報「相同」,而回報裡沒寫是用哪個工具、在哪一層比的。
  • 用來比對的是該系統文件教的第一個檢視指令,而它底下還有一層。
  • 同一個工具在別的比對上給過正確的差異,於是這一次沒被質疑。
  • 時間量測回報無差異,而結構層的比對沒跑。
  • 零差異的結論直接推進到結案,沒有引出任何下一步。