孤陋寡聞了,原來 MySQL 還能這么寫?
最近給一個我從來沒碰過的老系統(tǒng)加點兒功能,本來連測試環(huán)境的數(shù)據(jù)庫一切都很順利,但是為了保證功能在生產(chǎn)數(shù)據(jù)上沒有問題,就準(zhǔn)備把一部分生產(chǎn)數(shù)據(jù)搞到我本地環(huán)境上測一下。
結(jié)果,果然出現(xiàn)了問題,代碼問題就是這樣,總在不經(jīng)意間來到。
依我我知,想要不出現(xiàn)代碼問題,最好的方式就是——不寫代碼!
出現(xiàn)問題,咱就解決啊,我一看日志控制臺一大堆紅色報錯,不對啊,這都不是我剛加的功能報出來的,這樣的話,我就放心了。
摘出來一條錯誤信息是下面這樣的,是 SQL 查詢語句報錯了,報錯信息很明顯,就是說GROUP BY 后面的條件沒有在前面的 SELECT Columns 列表里。
一看其中有兩個關(guān)鍵點:
- GROUP BY
- sql_mode=only_full_group_by
1055 - Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'c.type' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by, Time: 0.001000s
我把 SQL 簡化了一下就是下面這個樣子:
select u.id,u.age from user u GROUP BY u.province;
一看這條 SQL,我心想,這SQL 本身就有問題啊,怎么就在系統(tǒng)中呆了這么長時間,但是在測試和生產(chǎn)環(huán)境確實就是正常運行的。
我本地一直用MySQL5.7,再看測試和生產(chǎn)數(shù)據(jù)庫,也是 5.7,就是小版本不太一樣,按往常經(jīng)驗,小版本應(yīng)該不會有這么大影響。
那肯定就是配置的問題,那肯定就是報錯信息中提到的 sql_mode=only_full_group_by 這個,原諒我孤陋寡聞了,用了 MySQL 這么久,從來沒聽過這玩意,而且用 GROUP BY就是為了分組聚合,GROUP BY后面的條件要出現(xiàn)在 SELECT 列表里不是很正常嗎,除非有兩個列有同樣的作用,比如一個名稱,一個編碼,用編碼分組,顯示名稱。要不然分組的意義在哪里呢?
但是系統(tǒng)已經(jīng)運行了很長時間了,那這 SQL 一定有他存在的意義,不管那么多了,看問題就好了。
sql_mode
然后我給自己科普了一下 sql_mode。
sql_mode 是 MySQL 的一個系統(tǒng)變量,用來控制 MySQL 服務(wù)器的 SQL 語法和行為的處理方式。通過配置不同的 sql_mode 值,MySQL 可以在 SQL 語法檢查、數(shù)據(jù)完整性約束、以及查詢處理等方面進(jìn)行不同的操作。
總之,就是 MySQL 會根據(jù)這個配置的內(nèi)容,來靈活的進(jìn)行語法檢查、數(shù)據(jù)約束等操作,加入的變量越多,控制的就越嚴(yán)格。
發(fā)現(xiàn)從 MySQL 5.7 開始,sql_mode 加了很多變量,ONLY_FULL_GROUP_BY就在其中。
使用 SELECT @@sql_mode;可以查詢數(shù)據(jù)庫中 sql_mode 配置的變量有哪些,這是 5.7 版本的默認(rèn)配置。
ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
既然是變量,就是可以改的,所以,這些變量可以動態(tài)的增減,或者索性全部去掉。
那肯定就是測試和生產(chǎn)環(huán)境改了這個配置了,上去一查,果不其然,那叫一個干凈。后來問之前的同事,了解到之前用的是更早的 MySQL 版本,后來統(tǒng)一升級到了 5.7,然后發(fā)現(xiàn)這個問題,所以改了配置。
ONLY_FULL_GROUP_BY
別的不說,只說 ONLY_FULL_GROUP_BY,當(dāng)數(shù)據(jù)庫中啟用了 ONLY_FULL_GROUP_BY 模式后,就要求在 GROUP BY 查詢中,SELECT 子句中的每一列都必須要么出現(xiàn)在 GROUP BY 子句中,要么應(yīng)用聚合函數(shù)(如 COUNT()、SUM()、MAX()、MIN() 等)。
這個例子中就是,province這個字段沒有在前面 SELECT 的字段列表中。我從剛用 MySQL 時一直都是按照 GROUP BY后面的列必須在前面的查詢列中來做的,沒想到這個還能改。
select u.id,u.age from user u GROUP BY u.province;
這種不行,下面這個也不行,因為 SELECT 查詢列表中的 u.id 不在 GROUP BY 后面的條件中
select u.id,u.age from user u GROUP BY u.age;
改成下面這樣才行
select u.id,u.age from user u GROUP BY u.age,u.id;
-- 或者
select u.age from user u GROUP BY u.age;
或者,還有一種情況,可以允許 SELECT 中存在 GROUP BY 后面沒有的列,就是加 聚合函數(shù)。
這應(yīng)該是最常規(guī)的用法了。
select max(u.id),u.age from user u GROUP BY u.age;
除了影響 GROUP BY外,還會影響 ORDER BY,看下面這條語句,當(dāng)開啟 ONLY_FULL_GROUP_BY后,會報錯
SELECT DISTINCT
b.title,
b.create_time
FROM
b_user b
ORDER BY
b.create_time DESC,
b.update_time DESC
報錯信息:
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'b.update_time DESC' at line 24, Time: 0.001000s
因為update_time字段不在 SELECT 后面,當(dāng)然這還是因為加了 DISTINCT。當(dāng)關(guān)閉 ONLY_FULL_GROUP_BY后,就能正常執(zhí)行了。
關(guān)閉 ONLY_FULL_GROUP_BY 模式
如果真的碰到從低版本升級上來的,系統(tǒng)中有很多這樣不符合 ONLY_FULL_GROUP_BY規(guī)范的語句,最省事的辦法就是直接關(guān)掉。
最省事兒的方法就是直接改 MySQL 配置文件,找到my.cnf配置文件,將其中的 sql_mode 改成下面這樣
sql_mode = ""
然后重啟就好了。
最后
建議沒有特殊情況,還是打開ONLY_FULL_GROUP_BY,這樣能保證你的查詢結(jié)果不會因為你寫錯 SQL 而出現(xiàn)莫名奇妙的數(shù)據(jù),數(shù)據(jù)庫會及時給你拋出錯誤,避免你對著一串 SQL 查錯時一面懵。
除非你能明確地知道你為什么需要把 ONLY_FULL_GROUP_BY關(guān)掉。