和小曼一起走到MySQL行的盡頭

小曼,重慶人,性格雖然內斂,但卻是一位資深段子手。去年和我一起入職,工位坐我旁邊後,承包了我半年的笑點。

我們還曾經一起去過那個被稱作 “MySQL行盡頭” 的地方。

那是一個普通的下午,耳邊都是趕着需求的鍵盤聲,我也碼得正嗨皮,就在這時突然傳來測試小姐姐的一聲

“(╯°Д°)╯︵ ┻━┻小曼,你的 SQL 報錯啦!”

我倆四目相對,眉頭一皺,嗯?這 SQL 不是我們倆昨天一起看過的嗎?而且在研發庫上還成功運行了的,竟然報錯了。

沒辦法,只能先停下手邊工作,把 SQL 領回來看看:

ALTER TABLE t ADD x VARCHAR(300);

這就是個普通的 DML 語句啊,爲 t 表增加一個 x 字段,其類型爲 VARCHAR,並且允許最大的字符長度 300。

看起來沒什麼毛病啊,那再看看在測試庫上報了啥錯誤:

[Err] 1118 - Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs

大致意思是:行過大,超出了 65535,需要將字段修改爲 TEXT 或 BLOBs 。

小曼,你看我們添加的字段 VARCHAR 沒有大於 65535 啊?不是寫的 300 嗎?怎麼會行過大呢?

ε=(´ο`*))) 唉,一看你就沒讀過《MySQL技術內幕》,快去好好補補,雖然 VARCHAR(M) 中的 M 最大可以是 65535,但是在 MySQL 中規定了所有 VARCHAR 字段的長度總和不能超過65535。”

(´・ω・`) 原來是這樣!

所以,是 t 表的 VARCHAR 字段的長度之和 > 65535了?那又爲什麼研發庫能執行成功,測試庫卻執行失敗?莫非兩個庫的 t 表存在不一致。

我們導出表結構一對比,果然發現有兩個 VARCHAR 的字段存在不一致,下面是兩個庫 t 表中 VARCHAR 字段的長度總和:

sum_varchar_test = 21735
sum_varchar_dev = 21035

測試庫比研發庫大了整整700!(不知道是哪位改了數據庫,沒同步 > <)

我再看了一眼表結構,VARCHAR 長度上千的字段比比皆是,這分明是不給後人留活路嘛,怪不得我們加不了字段。

可是還是不對啊,小曼,剛剛不是說長度總和大於 65535 嗎?測試庫的這也才 21735 啊。這不是還差很多嗎?

ε=(´ο`*))) 唉,VARCHAR(M) 中的 M 指的是字符長度,而 65535 指的是字節長度,我們 t 表用的是 utf8 編碼,utf8 編碼每個字符佔 1~3 字節,考慮每個字符可能最大 3 字節,所以 t 表中 VARCHAR 字段的字符長度總和不能超過 65535 / 3 = 21845。而想在測試庫上再加個 VARCHAR(300) 的字段確實是超過了限制。

(´・ω・`) 原來是這樣!

下面我們一起用SQL復現這個問題:

先創建一張表:

-- 每個字段上限1w字符
-- 又因爲是latin1編碼,所以也是1w字節
CREATE TABLE t (
a VARCHAR(10000),
b VARCHAR(10000),
c VARCHAR(10000),
d VARCHAR(10000),
e VARCHAR(10000),
f VARCHAR(10000)
)CHARSET=latin1, ENGINE=InnoDB;

再嘗試修改表結構:

ALTER TABLE t ADD x VARCHAR(10000);
[Err] 1118 - Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs

錯誤如約而至,原因我們也已知曉,70000 > 65535,那我們再嘗試修改下 SQL:

ALTER TABLE t ADD x VARCHAR(5535);
[Err] 1118 - Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs

仍然報錯,經過反覆測試後,新增的 VARCHAR 最大長度只能是 5520:

ALTER TABLE t ADD x VARCHAR(5520);
Query OK, 0 rows affected (0.02 sec)

最大隻能是 65520,和 65535 還差了 15,這是因爲還有其他開銷(VARCHAR的長度標識 2 個字節 * 7 + NULL 標識位的1個字節)。

到這裏,問題基本清楚了。原來 MySQL 的行也是有盡頭的,雖然 VARCHAR 具有可變長的特點,好用,但也不能亂用,畢竟還有 65535 字節在限制着我們。

小曼,那我們是不是按 MySQL 給建議,把字段改成 TEXT 和 BLOBs 就可以跨越限制,不會再出現這個問題?

ε=(´ο`*))) 唉,MySQL 設計數據結構的時候就已經規定了“一切皆有盡頭”,TEXT 和 BLOBs 也不例外,所以仍然存在超出限制的可能。它們的具體限制你去翻翻 MySQL 手冊 11.7節吧,不說了,我要改BUG了。

(´・ω・`) 好的,小本本記下來,MySQL手冊 11.7節,原來“一切皆有盡頭”!

禪定時刻

上述已經將問題基本定位清楚,MySQL 的限制讓我們不能繼續添加字段,但同時這也正提醒着我們設計的重要性。會出現這個問題的真正原因實則正是,我們使用數據庫字段類型不當,那麼如何解決該問題,我想大家也應該都知道了。

最後想說,雖然一切皆有盡頭,但只要我們用合理的設計去解決,便能打破有限,創造無限。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章