8個(gè)MySQL常見的新手SQL錯(cuò)誤用法詳解
前言
在MySQL學(xué)習(xí)和開發(fā)的過程中,新手很容易寫出“能跑但有問題”的SQL:要么性能極差,要么結(jié)果錯(cuò)誤,甚至引發(fā)數(shù)據(jù)誤刪的嚴(yán)重故障。這些錯(cuò)誤往往不是語法錯(cuò)誤,而是邏輯錯(cuò)誤、性能錯(cuò)誤或安全錯(cuò)誤,很難被發(fā)現(xiàn),但危害極大。
本文整理了8種最常見的新手SQL錯(cuò)誤用法,每種錯(cuò)誤都配詳細(xì)的“錯(cuò)誤示例”、“危害分析”、“正確寫法”和“避坑指南”,幫助你從入門開始就養(yǎng)成良好的SQL習(xí)慣,避免踩坑。
錯(cuò)誤一:濫用 SELECT *,只圖省事不看后果
錯(cuò)誤用法
很多新手圖省事,查詢時(shí)直接寫SELECT *,不管需要多少字段:
-- 錯(cuò)誤寫法:查詢所有字段 SELECT * FROM user_info WHERE city = '武漢';
為什么錯(cuò)
- 浪費(fèi)磁盤IO和網(wǎng)絡(luò)帶寬:查詢了很多不需要的字段(比如大字段
content、avatar),增加了磁盤讀取和網(wǎng)絡(luò)傳輸?shù)拈_銷; - 無法使用覆蓋索引:如果只查詢需要的字段,且這些字段都在聯(lián)合索引中,可以使用“覆蓋索引”,無需回表,性能提升數(shù)倍;但
SELECT *必須回表查整行數(shù)據(jù),性能差; - 表結(jié)構(gòu)變更風(fēng)險(xiǎn):如果表結(jié)構(gòu)新增或刪除了字段,
SELECT *的結(jié)果會(huì)變化,可能導(dǎo)致應(yīng)用層報(bào)錯(cuò)。
正確用法
只查詢業(yè)務(wù)需要的字段:
-- 正確寫法:只查詢需要的字段 SELECT id, username, phone, age FROM user_info WHERE city = '武漢';
如果這些字段在聯(lián)合索引(idx_city_username_phone_age)中,就是覆蓋索引,無需回表,性能極佳。
避坑指南
- **永遠(yuǎn)不要寫SELECT ***,除非你真的需要所有字段;
- 寫SQL前先想清楚:業(yè)務(wù)到底需要哪些字段?只查這些字段;
- 用EXPLAIN查看執(zhí)行計(jì)劃,如果
Extra列有Using index,說明用到了覆蓋索引,很好。
錯(cuò)誤二:不帶 WHERE 條件的 UPDATE/DELETE,高危操作!
錯(cuò)誤用法
這是最危險(xiǎn)的錯(cuò)誤,一不小心就會(huì)全表更新或刪除:
-- 錯(cuò)誤寫法:不帶WHERE條件的UPDATE,全表更新! UPDATE user_info SET age = 28; -- 錯(cuò)誤寫法:不帶WHERE條件的DELETE,全表刪除! DELETE FROM user_info;
為什么錯(cuò)
- 全表操作:不帶WHERE條件,會(huì)更新/刪除表中的所有數(shù)據(jù),無法回滾(除非在事務(wù)中);
- 線上故障:如果在生產(chǎn)環(huán)境執(zhí)行,會(huì)導(dǎo)致所有數(shù)據(jù)丟失或錯(cuò)誤,引發(fā)嚴(yán)重的線上事故,甚至需要離職賠償。
正確用法
必須帶WHERE條件:
-- 正確寫法:帶WHERE條件,只更新指定行 UPDATE user_info SET age = 28 WHERE id = 1; -- 正確寫法:帶WHERE條件,只刪除指定行 DELETE FROM user_info WHERE id = 1;
執(zhí)行前先SELECT驗(yàn)證:執(zhí)行UPDATE/DELETE前,先用SELECT查看WHERE條件匹配的行數(shù),確認(rèn)無誤后再執(zhí)行:
-- 先SELECT驗(yàn)證:查看匹配的行數(shù) SELECT COUNT(*) FROM user_info WHERE id = 1; -- 確認(rèn)只有1行后,再執(zhí)行UPDATE/DELETE
生產(chǎn)環(huán)境用邏輯刪除替代物理刪除:不要直接DELETE,用is_deleted字段標(biāo)記刪除:
-- 邏輯刪除:更新is_deleted為1,而不是DELETE UPDATE user_info SET is_deleted = 1 WHERE id = 1;
避坑指南
- UPDATE/DELETE必須帶WHERE條件,不帶條件絕對(duì)不執(zhí)行;
- 執(zhí)行前先SELECT驗(yàn)證,確認(rèn)匹配的行數(shù)和數(shù)據(jù);
- 生產(chǎn)環(huán)境開啟SQL審核,禁止不帶WHERE的UPDATE/DELETE;
- 盡量用邏輯刪除,避免物理刪除,誤刪后還能恢復(fù)。
錯(cuò)誤三:LIKE 通配符在開頭,索引失效全表掃描
錯(cuò)誤用法
用LIKE模糊查詢時(shí),把通配符%放在開頭:
-- 錯(cuò)誤寫法:通配符在開頭,索引失效 SELECT * FROM user_info WHERE username LIKE '%張三%'; -- 更糟:通配符只在開頭 SELECT * FROM user_info WHERE username LIKE '%張三';
為什么錯(cuò)
MySQL的聯(lián)合索引遵循最左前綴原則,LIKE查詢只有通配符在結(jié)尾時(shí)才能用到索引:
LIKE '張三%'能用索引(前綴匹配);LIKE '%張三'索引失效(后綴匹配);LIKE '%張三%'索引失效(中間匹配)。
通配符在開頭時(shí),MySQL無法利用索引的有序性,只能全表掃描,性能極差。
正確用法
盡量用前綴匹配:
-- 正確寫法:通配符在結(jié)尾,能用索引 SELECT * FROM user_info WHERE username LIKE '張三%';
如果必須用中間/后綴匹配,用全文索引或Elasticsearch:
如果業(yè)務(wù)必須用%張三%這樣的模糊查詢,不要用LIKE,改用:
- MySQL的全文索引(FULLTEXT INDEX);
- 或者把數(shù)據(jù)同步到Elasticsearch,用ES做模糊查詢,性能更好。
避坑指南
- LIKE查詢盡量用前綴匹配,通配符只放結(jié)尾;
- 用EXPLAIN查看執(zhí)行計(jì)劃,如果
type列是ALL,說明全表掃描,索引失效; - 必須用中間/后綴匹配時(shí),用全文索引或ES,不要用LIKE。
錯(cuò)誤四:在索引列上用函數(shù)/表達(dá)式,索引白白浪費(fèi)
錯(cuò)誤用法
在索引列上使用函數(shù)(比如YEAR()、DATE())或表達(dá)式(比如id + 1):
-- 錯(cuò)誤寫法:在索引列create_time上用YEAR()函數(shù),索引失效 SELECT * FROM user_info WHERE YEAR(create_time) = 2026; -- 錯(cuò)誤寫法:在索引列id上用表達(dá)式,索引失效 SELECT * FROM user_info WHERE id + 1 = 2;
為什么錯(cuò)
MySQL的索引是對(duì)列的原始值建立的B+樹,如果在列上用了函數(shù)或表達(dá)式,索引的有序性就被破壞了,優(yōu)化器無法使用索引,只能全表掃描。
正確用法
把函數(shù)/表達(dá)式移到等號(hào)的右邊,讓索引列保持“干凈”:
-- 正確寫法:把YEAR()移到右邊,用范圍查詢,能用索引 SELECT * FROM user_info WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'; -- 正確寫法:把表達(dá)式移到右邊,id保持干凈,能用索引 SELECT * FROM user_info WHERE id = 2 - 1;
如果必須在列上用函數(shù),可以創(chuàng)建函數(shù)索引(MySQL 8.0+支持):
-- 創(chuàng)建函數(shù)索引 CREATE INDEX idx_year_create_time ON user_info ((YEAR(create_time))); -- 現(xiàn)在可以用YEAR()查詢了,能用到函數(shù)索引 SELECT * FROM user_info WHERE YEAR(create_time) = 2026;
避坑指南
- 永遠(yuǎn)不要在索引列上用函數(shù)/表達(dá)式,保持索引列“干凈”;
- 把函數(shù)/表達(dá)式移到等號(hào)右邊,用范圍查詢替代;
- 如果必須用函數(shù),MySQL 8.0+可以創(chuàng)建函數(shù)索引;
- 用EXPLAIN查看執(zhí)行計(jì)劃,確認(rèn)索引是否生效。
錯(cuò)誤五:隱式類型轉(zhuǎn)換,索引失效還可能查錯(cuò)數(shù)據(jù)
錯(cuò)誤用法
查詢時(shí),字段類型和參數(shù)類型不一致,導(dǎo)致隱式類型轉(zhuǎn)換:
-- 錯(cuò)誤寫法:phone是varchar類型,卻用數(shù)字13800138000查詢,隱式類型轉(zhuǎn)換 SELECT * FROM user_info WHERE phone = 13800138000; -- 錯(cuò)誤寫法:id是bigint類型,卻用字符串'1'查詢,隱式類型轉(zhuǎn)換 SELECT * FROM user_info WHERE id = '1';
為什么錯(cuò)
索引失效:隱式類型轉(zhuǎn)換會(huì)破壞索引的有序性,優(yōu)化器無法使用索引,只能全表掃描;
查錯(cuò)數(shù)據(jù):隱式類型轉(zhuǎn)換可能導(dǎo)致查詢結(jié)果錯(cuò)誤。比如phone是varchar,phone = 13800138000會(huì)把phone的字符串轉(zhuǎn)換為數(shù)字,'13800138000a'這樣的字符串也會(huì)被轉(zhuǎn)換為13800138000,導(dǎo)致查錯(cuò)數(shù)據(jù)。
正確用法
保持字段類型和參數(shù)類型一致:
-- 正確寫法:phone是varchar,用字符串'13800138000'查詢 SELECT * FROM user_info WHERE phone = '13800138000'; -- 正確寫法:id是bigint,用數(shù)字1查詢 SELECT * FROM user_info WHERE id = 1;
避坑指南
- 保持字段類型和參數(shù)類型一致,避免隱式類型轉(zhuǎn)換;
- 建表時(shí),選擇合適的數(shù)據(jù)類型:手機(jī)號(hào)、身份證號(hào)用
varchar,不要用bigint; - 用EXPLAIN查看執(zhí)行計(jì)劃,如果
type列是ALL,且字段類型不一致,可能是隱式類型轉(zhuǎn)換導(dǎo)致的。
錯(cuò)誤六:濫用 NOT IN / <>,索引失效還可能結(jié)果錯(cuò)誤
錯(cuò)誤用法
很多新手喜歡用NOT IN或<>(不等于)來排除數(shù)據(jù):
-- 錯(cuò)誤寫法:NOT IN,可能索引失效
SELECT * FROM user_info WHERE city NOT IN ('武漢', '北京');
-- 錯(cuò)誤寫法:<>,可能索引失效
SELECT * FROM user_info WHERE age <> 28;
為什么錯(cuò)
索引失效:NOT IN和<>屬于“負(fù)向查詢”,MySQL優(yōu)化器通常不會(huì)選擇索引,而是全表掃描,性能差;
NOT IN包含NULL時(shí)結(jié)果錯(cuò)誤:如果NOT IN的列表中有NULL,整個(gè)查詢會(huì)返回空結(jié)果,因?yàn)?code>NULL的三值邏輯導(dǎo)致的。
正確用法
盡量用正向查詢替代:如果業(yè)務(wù)允許,用IN替代NOT IN,用=替代<>;
如果必須用負(fù)向查詢,用EXISTS或LEFT JOIN IS NULL替代:
-- 用NOT EXISTS替代NOT IN,性能更好,且不受NULL影響 SELECT * FROM user_info u WHERE NOT EXISTS ( SELECT 1 FROM exclude_city e WHERE e.city = u.city ); -- 用LEFT JOIN + IS NULL替代NOT IN SELECT u.* FROM user_info u LEFT JOIN exclude_city e ON u.city = e.city WHERE e.city IS NULL;
NOT IN列表中絕對(duì)不要包含NULL:
-- 錯(cuò)誤:NOT IN列表中有NULL,返回空結(jié)果
SELECT * FROM user_info WHERE city NOT IN ('武漢', NULL);
-- 正確:NOT IN列表中沒有NULL
SELECT * FROM user_info WHERE city NOT IN ('武漢', '北京');
避坑指南
- 盡量避免用NOT IN和<>,優(yōu)先用正向查詢;
- 如果必須用負(fù)向查詢,用NOT EXISTS或LEFT JOIN IS NULL替代;
- NOT IN列表中絕對(duì)不要包含NULL,否則結(jié)果錯(cuò)誤;
- 用EXPLAIN查看執(zhí)行計(jì)劃,確認(rèn)是否用到索引。
錯(cuò)誤七:用 ORDER BY RAND() 隨機(jī)查詢,性能極差
錯(cuò)誤用法
很多新手用ORDER BY RAND()來隨機(jī)查詢數(shù)據(jù):
-- 錯(cuò)誤寫法:ORDER BY RAND(),全表掃描+全表排序,性能極差 SELECT * FROM user_info ORDER BY RAND() LIMIT 10;
為什么錯(cuò)
ORDER BY RAND()的執(zhí)行邏輯是:
- 為表中的每一行生成一個(gè)隨機(jī)數(shù);
- 按照隨機(jī)數(shù)對(duì)所有行進(jìn)行排序(通常是文件排序filesort);
- 取前N條。
如果表有100萬行,就需要生成100萬個(gè)隨機(jī)數(shù),然后對(duì)100萬行進(jìn)行排序,磁盤IO和CPU開銷極大,查詢耗時(shí)可能達(dá)到秒級(jí)甚至分鐘級(jí)。
正確用法
方案一:利用自增主鍵ID范圍隨機(jī)(推薦,性能最高)
如果表有連續(xù)的自增主鍵ID:
-- 步驟1:獲取ID的最小值和最大值 SELECT MIN(id) AS min_id, MAX(id) AS max_id FROM user_info; -- 步驟2:在應(yīng)用層生成10個(gè)不重復(fù)的隨機(jī)ID(比如123, 456...) -- 步驟3:通過ID精準(zhǔn)查詢 SELECT * FROM user_info WHERE id IN (123, 456, 789, ...);
方案二:覆蓋索引+ORDER BY RAND()(折中方案)
如果沒有連續(xù)的自增主鍵,先通過覆蓋索引隨機(jī)查ID,再回表:
-- 先通過覆蓋索引隨機(jī)查10個(gè)ID,排序的數(shù)據(jù)量小 SELECT t.* FROM user_info t INNER JOIN ( SELECT id FROM user_info ORDER BY RAND() LIMIT 10 ) tmp ON t.id = tmp.id;
避坑指南
- 絕對(duì)不要直接用SELECT * ORDER BY RAND(),性能極差;
- 優(yōu)先用自增主鍵ID范圍隨機(jī),性能最高;
- 如果沒有連續(xù)主鍵,用覆蓋索引+ORDER BY RAND(),減少排序的數(shù)據(jù)量;
- 大表隨機(jī)查詢,考慮用Redis緩存ID列表,在應(yīng)用層隨機(jī)。
錯(cuò)誤八:忽略 NULL 值的三值邏輯,結(jié)果錯(cuò)誤
錯(cuò)誤用法
很多新手對(duì)NULL的三值邏輯不了解,寫出錯(cuò)誤的SQL:
-- 錯(cuò)誤寫法:用= NULL判斷NULL,永遠(yuǎn)返回FALSE SELECT * FROM user_info WHERE email = NULL; -- 錯(cuò)誤寫法:NOT IN列表中有NULL,返回空結(jié)果 SELECT * FROM user_info WHERE id NOT IN (1, 2, NULL); -- 錯(cuò)誤寫法:COUNT(email)會(huì)忽略NULL值,結(jié)果不對(duì) SELECT COUNT(email) FROM user_info;
為什么錯(cuò)
MySQL的邏輯判斷有三種結(jié)果:TRUE、FALSE、UNKNOWN,而NULL代表“未知”:
= NULL:結(jié)果是UNKNOWN,不會(huì)返回任何行;NOT IN (..., NULL):結(jié)果是UNKNOWN,整個(gè)查詢返回空;COUNT(列名):會(huì)忽略NULL值,只統(tǒng)計(jì)非NULL的行數(shù);COUNT(*)才會(huì)統(tǒng)計(jì)所有行數(shù)。
正確用法
用IS NULL / IS NOT NULL判斷NULL:
-- 正確寫法:用IS NULL判斷NULL SELECT * FROM user_info WHERE email IS NULL; -- 正確寫法:用IS NOT NULL判斷非NULL SELECT * FROM user_info WHERE email IS NOT NULL;
NOT IN列表中不要包含NULL:
-- 正確:NOT IN列表中沒有NULL SELECT * FROM user_info WHERE id NOT IN (1, 2, 3);
區(qū)分COUNT(*)和COUNT(列名):
-- COUNT(*):統(tǒng)計(jì)所有行數(shù),包括NULL值 SELECT COUNT(*) FROM user_info; -- COUNT(email):統(tǒng)計(jì)email非NULL的行數(shù) SELECT COUNT(email) FROM user_info;
避坑指南
- 永遠(yuǎn)不要用= NULL或<> NULL,用IS NULL / IS NOT NULL;
- NOT IN列表中絕對(duì)不要包含NULL;
- 區(qū)分COUNT(*)和COUNT(列名):統(tǒng)計(jì)所有行數(shù)用COUNT(*),統(tǒng)計(jì)非NULL行數(shù)用COUNT(列名);
- 建表時(shí),盡量給字段設(shè)置
NOT NULL和默認(rèn)值,避免NULL值帶來的問題。
總結(jié):新手SQL避坑的5個(gè)核心習(xí)慣
看完這8種錯(cuò)誤,我們可以總結(jié)出新手SQL避坑的5個(gè)核心習(xí)慣:
- 永遠(yuǎn)不寫SELECT ,只查需要的字段,盡量用覆蓋索引;
- UPDATE/DELETE必須帶WHERE條件,執(zhí)行前先SELECT驗(yàn)證,生產(chǎn)環(huán)境用邏輯刪除;
- 保持索引列“干凈”:不用函數(shù)/表達(dá)式、不用隱式類型轉(zhuǎn)換、LIKE通配符只放結(jié)尾;
- 盡量用正向查詢:避免NOT IN/<>,用EXISTS/LEFT JOIN替代;
- 重視NULL值的三值邏輯:用IS NULL判斷NULL,NOT IN列表不含NULL,區(qū)分COUNT(*)和COUNT(列名)。
最后,寫SQL后一定要用EXPLAIN查看執(zhí)行計(jì)劃,重點(diǎn)看type(訪問類型)、key(實(shí)際用到的索引)、rows(預(yù)計(jì)掃描的行數(shù))、Extra(額外信息),確認(rèn)SQL的性能符合預(yù)期,避免踩坑。
以上就是8個(gè)MySQL常見的新手SQL錯(cuò)誤用法詳解的詳細(xì)內(nèi)容,更多關(guān)于MySQL新手SQL錯(cuò)誤用法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MySQL事務(wù)的隔離性是如何實(shí)現(xiàn)的
最近做了一些分布式事務(wù)的項(xiàng)目,對(duì)事務(wù)的隔離性有了更深的認(rèn)識(shí),后續(xù)寫文章聊分布式事務(wù)。今天就復(fù)盤一下單機(jī)事務(wù)的隔離性是如何實(shí)現(xiàn)的?感興趣的可以了解一下-2021-09-09
phpmyadmin中為站點(diǎn)設(shè)置mysql權(quán)限的圖文方法
在一個(gè)服務(wù)器上一般來講都不止一個(gè)站點(diǎn),更不止一個(gè)MySQL(和PHP搭配之最佳組合)數(shù)據(jù)庫。2011-03-03
mysql錯(cuò)誤處理之ERROR 1665 (HY000)
最近一直在mysql的各個(gè)版本直接徘徊,這中間遇到了各種各樣的錯(cuò)誤,將已經(jīng)處理完畢的幾個(gè)錯(cuò)誤整理了一下,分享給大家,這次我們來看看錯(cuò)誤提示 ERROR 1665 (HY000)2014-07-07
一步步教你利用Mysql存儲(chǔ)過程造百萬級(jí)數(shù)據(jù)
因工作需要維護(hù)一張中建表數(shù)據(jù)內(nèi)置,所以得造數(shù)據(jù)所以使用存儲(chǔ)過程來造數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于如何一步步利用Mysql存儲(chǔ)過程造百萬級(jí)數(shù)據(jù)的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-03-03

