義符詳解:從通配符陷阱到性能優(yōu)化)
有一回做投訴工單報(bào)表要從一張幾萬(wàn)行的表里篩出“產(chǎn)品名稱中帶 5% 字樣”的記錄。我順手寫了WHERE product_name LIKE %5%%執(zhí)行完當(dāng)場(chǎng)愣住——全表幾乎全中。第一反應(yīng)是數(shù)據(jù)臟排查了大半天最后才發(fā)現(xiàn)鍋根本不在數(shù)據(jù)而是這條 SQL 里的%太“自作聰明”了。像“字段中含有轉(zhuǎn)義符”這類坑凡是寫過(guò)模糊匹配查詢的人基本都會(huì)踩一次區(qū)別只是有人半小時(shí)就繞出來(lái)了有人會(huì)為此懷疑人生。這篇就專門聊透這個(gè)事LIKE 里的%和_到底在干嘛、轉(zhuǎn)義符是怎么回事、不同數(shù)據(jù)庫(kù)怎么處理、工程代碼里怎么防御以及這類查詢的性能和索引陷阱。內(nèi)容以 MySQL 為主線兼顧 Oracle、SQL Server、PostgreSQL最后附上踩坑記錄和排查速查表。適合正在做數(shù)據(jù)查詢、報(bào)表統(tǒng)計(jì)、接口開(kāi)發(fā)的朋友尤其是被模糊查詢結(jié)果弄到一頭霧水的那幾位。1. 模糊匹配為什么會(huì)“栽”在轉(zhuǎn)義符上1.1 LIKE 里 % 和 _ 的真實(shí)身份先明確一個(gè)基本概念。LIKE做模糊匹配時(shí)模式串里有兩個(gè)特殊字符是通配符%匹配任意長(zhǎng)度的任意字符包括空字符串。_匹配且僅匹配任意單個(gè)字符。生活化類比%相當(dāng)于你搜索文件時(shí)輸入的*只要文件名里有一段對(duì)得上整串都能匹配_則更像下棋時(shí)的“一個(gè)空格”必須剛好有一個(gè)字符占住這個(gè)位置多一個(gè)少一個(gè)都不行。問(wèn)題在于如果業(yè)務(wù)字段的數(shù)據(jù)本身包含%或_比如“洗面奶 100% 正品”或者編碼規(guī)范里常見(jiàn)的KPI_2024_考核表那么這些字符在 LIKE 模式里會(huì)被當(dāng)成通配符來(lái)解釋查出來(lái)的結(jié)果自然不是字面含義。這是“字段中含有轉(zhuǎn)義符”的核心矛盾數(shù)據(jù)里的特殊字符和查詢語(yǔ)法的特殊字符撞車了。解決思路只有一個(gè)——告訴數(shù)據(jù)庫(kù)這里出現(xiàn)的%或_不是通配符是普通字符。這個(gè)動(dòng)作就叫“轉(zhuǎn)義”。1.2 一個(gè)典型的誤匹配現(xiàn)場(chǎng)用數(shù)據(jù)說(shuō)話。假設(shè)有一張投訴記錄表結(jié)構(gòu)很簡(jiǎn)單CREATE TABLE complaint_log ( id INT PRIMARY KEY, product_name VARCHAR(100), deal_rate VARCHAR(20) ); INSERT INTO complaint_log VALUES (1, 洗面奶促銷裝100%正品, 15%), (2, 洗發(fā)水500ml, 5%), (3, KPI_2024_考核表, NULL), (4, KPX2024考核表, NULL);業(yè)務(wù)需求查出所有商品名稱里帶KPI_2024的記錄。直覺(jué)寫法SELECT * FROM complaint_log WHERE product_name LIKE %KPI_2024%;這條 SQL 不會(huì)報(bào)錯(cuò)但結(jié)果里第 3 條和第 4 條都會(huì)出來(lái)。第 4 條KPX2024考核表并沒(méi)有_但因?yàn)開(kāi)被當(dāng)成“任意單個(gè)字符”KPI_2024中的_恰好匹配了X整條就命中了。再看%的例子。需求是查“包含 5% 這個(gè)字面值”的記錄SELECT * FROM complaint_log WHERE product_name LIKE %5%%;這個(gè)寫法更離譜第二個(gè)%是通配符等價(jià)于%5%于是只要字段里出現(xiàn)過(guò)數(shù)字 5就會(huì)全部命中。表里第 1、2 條都會(huì)查出來(lái)甚至某條記錄里帶個(gè)“5 元優(yōu)惠券”也會(huì)被撈上來(lái)。這兩段代碼看起來(lái)人畜無(wú)害實(shí)際跑起來(lái)全是坑。根本原因就是沒(méi)做轉(zhuǎn)義。1.3 不同數(shù)據(jù)庫(kù)的默認(rèn)轉(zhuǎn)義行為這里有個(gè)特別重要的點(diǎn)不同數(shù)據(jù)庫(kù)對(duì) LIKE 中轉(zhuǎn)義符的默認(rèn)處理是不一樣的。我整理了一個(gè)對(duì)照表方便你排查時(shí)對(duì)號(hào)入座。數(shù)據(jù)庫(kù)默認(rèn)轉(zhuǎn)義字符需要顯式 ESCAPE 嗎補(bǔ)充說(shuō)明MySQL\是默認(rèn)轉(zhuǎn)義符可以不寫但受NO_BACKSLASH_ESCAPES模式影響模式不統(tǒng)一時(shí)容易出隱性 bugSQL Server無(wú)默認(rèn)轉(zhuǎn)義符必須寫ESCAPE否則%和_永遠(yuǎn)是被通配還支持[]做單字符匹配容易混Oracle無(wú)默認(rèn)轉(zhuǎn)義符必須寫ESCAPE字符串字面量里的\處理也容易混亂PostgreSQL\是默認(rèn)轉(zhuǎn)義符建議顯式寫ESCAPE受standard_conforming_strings影響字面量寫法易混淆所以“為什么我在 MySQL 里寫了%\_%有效在 SQL Server 里卻什么都不匹配”這類問(wèn)題答案往往就在這個(gè)表里。SQL Server 不認(rèn)反斜杠轉(zhuǎn)義你必須顯式給出ESCAPE比如WHERE product_name LIKE %KPI\_2024\_% ESCAPE \;MySQL 里默認(rèn)能直接用反斜杠但如果數(shù)據(jù)庫(kù)處于NO_BACKSLASH_ESCAPES模式反斜杠也會(huì)失效。因此最穩(wěn)妥的做法是不要依賴數(shù)據(jù)庫(kù)的默認(rèn)行為每條 LIKE 都顯式聲明轉(zhuǎn)義符。2. 正確姿勢(shì)ESCAPE 子句與等價(jià)替代方案2.1 ESCAPE 子句的完整寫法SQL 標(biāo)準(zhǔn)提供了一套通用的解決方案在 LIKE 模式末尾加ESCAPE子句自定義一個(gè)轉(zhuǎn)義字符。語(yǔ)法格式WHERE 字段 LIKE 模式 ESCAPE 轉(zhuǎn)義字符;轉(zhuǎn)義字符的作用是當(dāng)它出現(xiàn)在%或_前面時(shí)后面的通配符就被“降級(jí)”為普通字符。用前面的5%場(chǎng)景舉例SELECT * FROM complaint_log WHERE product_name LIKE %5/%% ESCAPE /;拆開(kāi)看這段模式第一個(gè)%是通配符中間的/是自定義轉(zhuǎn)義符/%表示“字面意義上的百分號(hào)”最后的%又是通配符。整條表達(dá)的意思是字段里只要包含字符串5%無(wú)論前后有沒(méi)有其他內(nèi)容都算命中。匹配下劃線的寫法同理SELECT * FROM complaint_log WHERE product_name LIKE %KPI/_2024/_% ESCAPE /;這里要注意三點(diǎn)。第一轉(zhuǎn)義符與后面的%或_之間不能有空格必須連寫。因?yàn)榭崭癖旧硎瞧胀ㄗ址坏┎迦肟崭褶D(zhuǎn)義邏輯就斷了。第二轉(zhuǎn)義符只對(duì)它緊跟著的一個(gè)字符生效。如果要匹配兩個(gè)連續(xù)的特殊字符比如字符串100%%模式就要寫成%100/%%/%一個(gè)字符一個(gè)轉(zhuǎn)義位。第三如果字段本身包含反斜杠比如 Windows 路徑C:\Users\test推薦用/這類非歧義字符做轉(zhuǎn)義符避免和字符串字面量級(jí)別的反斜杠轉(zhuǎn)義疊加否則寫兩層轉(zhuǎn)義很容易數(shù)錯(cuò)斜杠。2.2 用字符串函數(shù)代替 LIKE繞開(kāi)通配符問(wèn)題如果不需要用%做真正的模糊匹配只需要“判斷字段中是否包含某個(gè)固定文本”那更干凈的做法是用字符串定位函數(shù)。這些函數(shù)搜索的是普通字符串不存在通配符語(yǔ)義也就不需要轉(zhuǎn)義。四個(gè)主流數(shù)據(jù)庫(kù)的寫法-- MySQL SELECT * FROM complaint_log WHERE LOCATE(5%, product_name) 0; -- Oracle SELECT * FROM complaint_log WHERE INSTR(product_name, 5%) 0; -- SQL Server SELECT * FROM complaint_log WHERE CHARINDEX(5%, product_name) 0; -- PostgreSQL SELECT * FROM complaint_log WHERE POSITION(5% IN product_name) 0;這套方案的優(yōu)點(diǎn)非常直觀不用管%、_、\搜索什么就是什么。適合“完全匹配子串但不關(guān)心前后綴”的場(chǎng)景比如在黑名單詞表里查包含違禁%的數(shù)據(jù)。缺點(diǎn)也有不像LIKE那樣能表達(dá)復(fù)雜的模糊模式比如“以 A 開(kāi)頭、中間包含 B、以 C 結(jié)尾”這種組合用函數(shù)就不好寫還得回到 LIKE 加正則那套。2.3 正則表達(dá)式方案適合復(fù)雜場(chǎng)景的兜底既然 LIKE 的通配符和轉(zhuǎn)義符容易出錯(cuò)另一個(gè)思路是換成正則表達(dá)式。在正則的世界里%和_是普通字符沒(méi)有通配符的語(yǔ)義所以不需要對(duì)它們轉(zhuǎn)義。以 MySQL 8 和 Oracle 的REGEXP_LIKE為例想查包含字面100%正品的記錄SELECT * FROM complaint_log WHERE REGEXP_LIKE(product_name, 100%正品);這里%在正則里就是百分號(hào)本身不需要像 LIKE 那樣加轉(zhuǎn)義。但別高興得太早正則有另一套自成體系的元字符.、*、[]、()、|等它們才是需要轉(zhuǎn)義的對(duì)象。比如要搜KPI_2024_考核表這個(gè)需求用正則是正常查但若要搜KPI.2024中的字面點(diǎn)號(hào)正則里就得寫成KPI\.2024。簡(jiǎn)單總結(jié)只搜固定子串優(yōu)先用字面函數(shù)LOCATE/INSTR/CHARINDEX。需要復(fù)雜模式、且字段里%、_出現(xiàn)頻繁時(shí)用正則表達(dá)式反而省事。LIKE 加ESCAPE則適合需求簡(jiǎn)單但就是想用 LIKE、團(tuán)隊(duì)習(xí)慣統(tǒng)一的場(chǎng)景。3. 工程代碼里如何穩(wěn)妥處理“轉(zhuǎn)義符”查詢3.1 參數(shù)化查詢不等于自動(dòng)轉(zhuǎn)義很多開(kāi)發(fā)者在代碼里用參數(shù)化查詢以為把用戶輸入原樣塞進(jìn)%...%就萬(wàn)事大吉。這是個(gè)很致命的誤解。參數(shù)化查詢解決的是 SQL 注入問(wèn)題它確保用戶輸入被當(dāng)成“值”而不是“SQL 片段”來(lái)解析。但在LIKE內(nèi)部%和_依然會(huì)被數(shù)據(jù)庫(kù)解釋為通配符。舉個(gè) Python 操作 MySQL 的例子keyword 5% sql SELECT * FROM complaint_log WHERE product_name LIKE %s params (f%{keyword}%,) # 錯(cuò)誤的做法% 還是通配符這樣做執(zhí)行后5%中的%依然通配一切結(jié)果還是會(huì)把所有含 5 的記錄全部撈出來(lái)。正確做法是在拼接 LIKE 模式之前先對(duì)關(guān)鍵詞做一次轉(zhuǎn)義處理def escape_like(keyword: str) - str: # 轉(zhuǎn)義順序有講究先反斜杠再 %再 _ return keyword.replace(\\, \\\\).replace(%, \\%).replace(_, \\_) keyword 5% escaped escape_like(keyword) # 得到 5\% sql SELECT * FROM complaint_log WHERE product_name LIKE %s ESCAPE \\\\ params (f%{escaped}%,)這里ESCAPE \\\\對(duì)應(yīng)數(shù)據(jù)庫(kù)里的反斜杠轉(zhuǎn)義符代碼里寫多少個(gè)斜杠經(jīng)常把人繞暈。所以我在工程里更推薦自定義一個(gè)不容易出錯(cuò)的轉(zhuǎn)義符def escape_like(keyword: str) - str: return keyword.replace(/, //).replace(%, /%).replace(_, /_) sql SELECT * FROM complaint_log WHERE product_name LIKE %s ESCAPE / params (f%{escape_like(keyword)}%,)這個(gè)方案一眼就能看明白用戶輸入里原來(lái)的/被寫成//%寫成/%_寫成/_。查詢時(shí)使用ESCAPE /可讀性比數(shù)反斜杠好太多。3.2 ORM 框架里同樣要預(yù)先處理ORM 并不負(fù)責(zé)幫你轉(zhuǎn)義 LIKE 的特殊字符。比如 Django 的 ORM# 看起來(lái)沒(méi)問(wèn)題實(shí)際生成 SQL 是 LIKE %5%%照樣全表?yè)?ComplaintLog.objects.filter(product_name__contains5%)__contains只是幫你拼了個(gè)%...%內(nèi)部的%該通配還通配。正確的做法是用extra或RawSQL把轉(zhuǎn)義邏輯顯式寫進(jìn) SQL 片段from django.db.models.expressions import RawSQL escaped escape_like(5%) qs ComplaintLog.objects.extra( where[product_name LIKE %s ESCAPE /], params[f%{escaped}%], )MyBatis 場(chǎng)景要區(qū)分#{}和${}。#{}是參數(shù)綁定安全但不能自動(dòng)處理 LIKE 通配符${}是文本拼接有注入風(fēng)險(xiǎn)不適合直接拼用戶輸入。穩(wěn)妥的寫法是在 XML 中用CONCAT并且提前在 Java 服務(wù)層完成轉(zhuǎn)義select idsearch resultType... SELECT * FROM complaint_log WHERE product_name LIKE CONCAT(%, #{escapedKeyword}, %) ESCAPE / /selectescapedKeyword在 Service 層已經(jīng)調(diào)用escape_like()處理過(guò)。這么做既避免注入又防住了通配符穿透。JPA 的Query同理LIKE :keyword里的keyword必須攜帶轉(zhuǎn)移后的模式。3.3 動(dòng)態(tài)拼 SQL 時(shí)的防守底線如果團(tuán)隊(duì)里還有人習(xí)慣用字符串拼接的方式生成查詢 SQL一定要立幾條規(guī)矩所有進(jìn)入 LIKE 模式的用戶輸入必須先走統(tǒng)一的轉(zhuǎn)義函數(shù)。轉(zhuǎn)義函數(shù)里必須包含對(duì)轉(zhuǎn)義符本身、%、_三者的處理缺一不可。拼接時(shí)LIKE必須顯式帶ESCAPE不允許依賴數(shù)據(jù)庫(kù)默認(rèn)行為。盡量限制輸入長(zhǎng)度比如 50 個(gè)字符以內(nèi)防止有人把超長(zhǎng)正則或通配符變體塞進(jìn)來(lái)。另外一個(gè)時(shí)常被忽略的坑是字段名本身帶特殊字符或數(shù)據(jù)庫(kù)保留字。比如熱詞里提到的“mysql 表中字段為關(guān)鍵字”如果字段命名是order、group、desc這類保留字查詢時(shí)必須用反引號(hào)包裹SELECT * FROM complaint_log WHERE order 1;這類問(wèn)題和轉(zhuǎn)義符屬于同一大類的“特殊字符處理”在開(kāi)發(fā)規(guī)范里值得專門加一條字段命名盡量避開(kāi)保留字和特殊符號(hào)實(shí)在避不開(kāi)要統(tǒng)一標(biāo)識(shí)符的引用方式。4. 含有轉(zhuǎn)義符的模糊查詢性能和索引怎么兼顧4.1 前導(dǎo)通配符會(huì)讓索引失效聊完正確性必須說(shuō)說(shuō)性能。很多人在排查轉(zhuǎn)義符問(wèn)題時(shí)往往忽略了 LIKE 查詢本身就存在索引陷阱。WHERE product_name LIKE %xxx%這種前后都帶通配符的寫法數(shù)據(jù)庫(kù)無(wú)法利用普通 B 樹(shù)索引只能走全表掃描。原因很簡(jiǎn)單優(yōu)化器沒(méi)法確定匹配的起點(diǎn)不知道應(yīng)該從索引樹(shù)的哪個(gè)位置開(kāi)始掃描。即使你加了ESCAPE把特殊字符轉(zhuǎn)義成了字面量這個(gè)性能問(wèn)題也不會(huì)消失。ESCAPE解決的是匹配結(jié)果的正確性索引失效的根源是前導(dǎo)%本身。如果你的查詢模式是固定的前綴比如LIKE abc%那普通索引還能用上。但“字段中間含有特殊字符”這類需求往往逃不掉全表掃描。這時(shí)就需要靠下面兩種手段兜底。4.2 用生成列或函數(shù)索引優(yōu)化MySQL 8.0 以上支持生成列。可以提前把字段中的特殊字符剝離出來(lái)存成獨(dú)立列再對(duì)這個(gè)列建索引查詢時(shí)直接走索引ALTER TABLE complaint_log ADD COLUMN product_name_clean VARCHAR(100) GENERATED ALWAYS AS (REPLACE(REPLACE(product_name, %, ), _, )) STORED; ALTER TABLE complaint_log ADD INDEX idx_clean (product_name_clean);這樣業(yè)務(wù)代碼里查“包含 5% 的記錄”時(shí)可以先按干凈列過(guò)濾或者直接對(duì)干凈列做常規(guī)查詢效率和正確性都得到了保障缺點(diǎn)是占存儲(chǔ)空間。Oracle 支持函數(shù)索引可以針對(duì)INSTR這類函數(shù)建索引CREATE INDEX idx_instr_rate ON complaint_log (INSTR(deal_rate, %));查詢時(shí)用WHERE INSTR(deal_rate, %) 0優(yōu)化器就有機(jī)會(huì)走函數(shù)索引。PostgreSQL 的表達(dá)式索引類似直接在查詢列上建立表達(dá)式CREATE INDEX idx_instr ON complaint_log ((POSITION(% IN deal_rate)));4.3 視圖解決不了性能問(wèn)題熱詞里有個(gè)高頻問(wèn)題“視圖可以加快查詢速度嗎”這個(gè)誤解在模糊查詢場(chǎng)景里特別常見(jiàn)。有人把WHERE product_name LIKE %5%%封裝成一個(gè)視圖指望查詢視圖能變快。可以直接給你結(jié)論視圖只是把一段 SQL 保存成了一個(gè)命名對(duì)象它不存儲(chǔ)數(shù)據(jù)也沒(méi)有自己的索引。查詢視圖等價(jià)于執(zhí)行視圖中嵌套的那條 SQL掃描成本和直接寫 LIKE 沒(méi)有任何區(qū)別。真正能提速的是合理的前綴匹配、覆蓋索引、全文索引或者干脆把特殊字符剝離后建生成列。想靠視圖解決模糊查詢性能問(wèn)題方向就錯(cuò)了。5. 常見(jiàn)問(wèn)題與實(shí)戰(zhàn)排查小抄5.1 快速對(duì)照表為了讓你遇到問(wèn)題時(shí)能快速定位我整理了下面這張表。問(wèn)題現(xiàn)象可能原因處理辦法查詢結(jié)果明顯過(guò)多字段里的%被當(dāng)成通配符使用ESCAPE轉(zhuǎn)義為字面量明明字段含_條件就是匹配不上_被當(dāng)成單字符占位符對(duì)_轉(zhuǎn)義或改用LOCATE等函數(shù)MySQL 里寫了\%無(wú)效數(shù)據(jù)庫(kù)處于NO_BACKSLASH_ESCAPES模式改用顯式ESCAPE /SQL Server 里\%無(wú)效SQL Server 無(wú)默認(rèn)轉(zhuǎn)義符必須寫ESCAPE \或用[]反斜杠路徑字段查詢不到字符串字面量轉(zhuǎn)義與 LIKE 轉(zhuǎn)義疊加換用非\的轉(zhuǎn)義符或參數(shù)綁定用戶輸入%導(dǎo)致全表匹配參數(shù)化查詢未處理 LIKE 通配符入?yún)⑶敖y(tǒng)一調(diào)用escape_like模糊查詢很慢前導(dǎo)%導(dǎo)致索引失效生成列/函數(shù)索引/全文索引把模糊查詢封裝成視圖想提速視圖不存儲(chǔ)數(shù)據(jù)不能加速改為對(duì)索引列做前綴查詢5.2 三個(gè)真實(shí)踩坑現(xiàn)場(chǎng)第一個(gè)坑業(yè)務(wù)編碼里大量使用下劃線。商品編碼規(guī)范是SZ_2024_001這類格式需求是“查詢 2024 年所有深圳商品”。同事寫的是LIKE %SZ_2024%結(jié)果把SZ02024、SZ12024都撈出來(lái)了因?yàn)檫@些_都可以被當(dāng)成任意單字符。排查時(shí)如果不先用一個(gè)小數(shù)據(jù)集驗(yàn)證 LIKE 語(yǔ)義很容易誤判為數(shù)據(jù)質(zhì)量問(wèn)題。第二個(gè)坑JSON 字段里做模糊匹配。有人把整個(gè) JSON 字符串存到字段里查詢時(shí)直接LIKE %type:A%。雖然 JSON 里的雙引號(hào)和冒號(hào)不是通配符但如果 JSON 數(shù)據(jù)里恰好含%或_一樣中招。更麻煩的是 JSON 字符串中的反斜杠轉(zhuǎn)義會(huì)疊加。這種情況不要用 LIKE直接用數(shù)據(jù)庫(kù)的 JSON 函數(shù)比如 MySQL 的JSON_EXTRACT既安全又高效。第三個(gè)坑SQL Server 的方括號(hào)。SQL Server 的 LIKE 支持[]做字符集匹配比如LIKE sales[_]2024可以匹配sales_2024。但如果你想匹配字面方括號(hào)就又得引入一層轉(zhuǎn)義。類似這種“一個(gè)數(shù)據(jù)庫(kù)一套規(guī)則”的細(xì)節(jié)跨數(shù)據(jù)庫(kù)移植時(shí)最容易翻車。建議所有涉及LIKE的 SQL 都寫清楚ESCAPE不要裸奔。5.3 特殊字段名和 IN 查詢的補(bǔ)充注意雖然本文主角是轉(zhuǎn)義符但“特殊字段處理”還有兩個(gè)經(jīng)常一起出現(xiàn)的坑值得多說(shuō)兩句。一是熱詞里提到的字段名是數(shù)據(jù)庫(kù)保留字。MySQL 用反引號(hào)Oracle 和 PostgreSQL 用雙引號(hào)SQL Server 用方括號(hào)。各數(shù)據(jù)庫(kù)的標(biāo)識(shí)符引用規(guī)則不一樣遷移 SQL 時(shí)很容易忽略。最省心的辦法是建表時(shí)就避免使用保留字命名。二是IN查詢報(bào)錯(cuò)或結(jié)果異常。常見(jiàn)原因包括列表里混入了 NULL導(dǎo)致結(jié)果缺少記錄字段類型和列表元素類型不一致列表過(guò)長(zhǎng)超過(guò)數(shù)據(jù)庫(kù)限制。排查順序應(yīng)該是先打印實(shí)際執(zhí)行的 SQL確認(rèn)列表內(nèi)容有沒(méi)有特殊字符再用COALESCE或IFNULL把 NULL 處理掉最后檢查字符集和排序規(guī)則是否統(tǒng)一。最后說(shuō)點(diǎn)實(shí)際的我在實(shí)際項(xiàng)目里被這種問(wèn)題折騰過(guò)好幾回之后總結(jié)出一條經(jīng)驗(yàn)凡是寫 LIKE 相關(guān)的需求第一件事就是問(wèn)清楚字段內(nèi)容里會(huì)不會(huì)出現(xiàn)%、_、\這些特殊字符。會(huì)就統(tǒng)一在代碼里做一次escape_like()數(shù)據(jù)庫(kù)層一律顯式ESCAPE /絕不依賴默認(rèn)行為。這個(gè)習(xí)慣養(yǎng)成了后面能少踩很多坑。再分享一個(gè)調(diào)試小技巧遇到“模糊查詢結(jié)果不對(duì)”時(shí)別急著改 SQL 來(lái)回試。先用一條SELECT把轉(zhuǎn)義后的模式串打出來(lái)看一眼往往能立刻發(fā)現(xiàn)%和_的位置錯(cuò)了。比如你原本以為模式是%5/%%打印出來(lái)發(fā)現(xiàn)成了%5%%/%那問(wèn)題就一目了然了。這種問(wèn)題最耗時(shí)間的環(huán)節(jié)從來(lái)不是寫修復(fù)語(yǔ)句而是確認(rèn)數(shù)據(jù)里的特殊字符到底是什么。把排查順序理順效率能提高不少。