MySQL 下因字段过多导致错误提示 “Row size too large ... not counting BLOBs, is 65535 ... TEXT or BLOBs” 的解决办法

因为标题的字数有限,所以在这里重新贴一下完整的错误提示,以便遇到此类问题的朋友能搜索到本文。

错误提示如下:

SQLSTATE[42000]: Syntax error or access violation: 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

我之所以会遇到这个错误提示,是因为最近的工作中涉及到一个来自第三方提供的数据源,为了存储它需要一个含有百余个字段的数据表。这里不讨论为什么要有这样的表结构存在,只分享对于这个首次遭遇的问题我是如何解决的。鉴于本文发布后很多朋友在留言中提问为什么不能分表,简单回答并罗列原因如下:

  • 这100多个字段已经是从第三方数据源的400多个字段中筛选之后的了,先天不足如此,大家体谅。
  • 这100多个字段在实际应用中基本上会一次性都要输出给客户端,所以在这种情况下分表的意义个人觉得不大,只会导致额外的跨表联合查询。
  • 不能,也没机会采用 mongoDb 等其它替代方案,因为我只是一个打工仔,所谓“架构”上的事轮不到我发言。

综上所述,这100多个字段的数据表我还得好好的去伺候和打理,但还是感谢大家的热心指点,谢谢!

其实这个问题的起因在错误提示中已经说的很清楚了,之所以没看懂并不是因为英文的问题,而是对 MySql 下的相关限制不了解。这就好像一个不懂篮球规则的人被裁判告知“三秒违例”时,虽然能听懂这4个字,但却不明白自己为什么错了一样。

简单的说就是,在 MySql 的表中,所有字段的长度的总和不能超过65535个字节。至于所谓的“长度”,可以粗暴的理解为:

`addr` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '地址',
`cross_st` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '交叉路口',
`srchst_num` varchar(10) COLLATE utf8_unicode_ci NOT NULL COMMENT '门牌号码',
`st` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT '街道名',

看到类似 varchar(255) 这种字段长度的定义了吗?说的“长度”就是它们。而且,255仅仅是指该字段允许存储255个字符,但实际应用时,一个字符要占3个字节的(UTF8编码下),这样算来,一个 varchar(255) 的字段就要占用 255 x 3 = 765个字节。回到我处的背景下,一个数据表中有百余个字段,这些字段又大多是文本内容,而我最初又在考虑冗余和偷懒的情况下将许多字段都定义的比较大,积累下来就触发了这个错误提示。

解决办法有两种:

办法1:谨慎合理型

在定义字段时,认真考虑和估算每个字段应有的长度和是否使用了恰当的类型。比如明明只为了储存性别的“男/女”,就别搞一个 varchar(255) 的字段来存储了。

办法2:粗暴野蛮型

在错误提示中提到了,虽然有65535这个限制,但是 TEXT 和 BLOBs 这2种类型的字段是不被计算在内的。所以,你大可以把所有需要存储文本内容的字段都设置为这2种类型。那叫一个霸气洒脱啊!

以上就是我的经验分享,因为自己也是一个老菜鸟,所以用词很不恰当也很粗鄙,仅供大家懵懂的看一下。而我在解决该问题时,参考了以下内容,请大家借鉴:

愿大家永远没机会去和一个有100多个字段的数据表打交道。

本帖已被设为精华帖!
本帖由 Summer 于 7年前 加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 23
Summer

为啥不考虑分表?真的无法分吗?

7年前 评论
Ryan

一百多个字段为啥不考虑MongoDB等储存

7年前 评论

@Summer 不考虑分表的原因在于,这个表中的100多个字段在实际使用中,十之八九会被同时调用;是的,没错,同时调用。所以,这种情况下再分表的意义也就是“好看”而已,相反却带来更多的跨表查询,有点得不偿失了。

7年前 评论

@Ryan 为啥不用 MongoDB?呵呵,我只能说作为一个码农,人微言轻,上面让干啥就干啥吧。太多事轮不到我来操心啊。

7年前 评论
颜⑧

blob*类拖累速度 查询慢 mongdb听说好多了

7年前 评论
颜⑧

@wyg27 也是,上头说为了速度读写文件都要完成

7年前 评论
nickfan

如果不是需要做独立检索的字段就没有必要独立开来,存储成json,然后$cast=>['xxx'=>'array',]好了,读取直接$model->xxx['subkey']来读写。
建那么多字长的表,上面的这种要求,确定不是猴子请来的逗逼?

7年前 评论

@颜⑧ 是啊,上头让吃屎也得尝尝啊。

7年前 评论

@nickfan 考虑过存为 json 的方法,不过项目中还面临一个问题就是,这个含100多个字段的表内的数据每天还要从一个第三方数据源 API 定时导入,而这个第三方 API 给的数据中,每条记录含有400多个字段(对,就是这么逗逼!)所以,还是经过筛选后剩下这100多个字段的。因此,为了导入时能容易些以及容易和数据源比对,也就放弃了自己存为 json 的尝试。

7年前 评论
颜⑧

@wyg27 这话说得大家都:yum: 应该请有经验的去

7年前 评论

@颜⑧ 哈哈,任何经验都很重要。

7年前 评论

我记得TEXT 和 BLOBs不被计算在内貌似是因为只要超过一定长度,就单独存放的,在原数据页只保留指针

但是也不能滥用BLOB类型,因为超过一定长度会带来磁盘临时表的读写问题和没法中索引问题,还浪费空间

要节省列字段长度的话,还是从小数据下手比较好.对于可枚举的类型用tinyint,节省空间也利于后续枚举类型增加时的扩展.

7年前 评论

@quericy 原来如此,多谢指教!

7年前 评论

当数据量很大的时候,经常会要慎重考虑数据库的结构是否合理,性能是否需要优化。
很多时候,许多现实都只是在历史遗留问题上追求业务的高速产出。
楼主,我理解你这种痛~
不过有空的话可以考虑优化一下哦~

7年前 评论

@edwin404 是的,“许多现实都只是在历史遗留问题上追求业务的高速产出”,一语中的!

7年前 评论

@Summer @Ryan 你们这两个家伙不知道分表和用其他驱动带来更多的灾难吗

7年前 评论

@lyhiving 请问兄台说的“更多灾难”是什么意思?愿闻其详。

7年前 评论

@wyg27
99%的时候我们要一次过读取某行内容,有时候排序,甚至无法设定db类型。

7年前 评论

@lyhiving 前半句“99%的时候我们要一次过读取某行内容”看懂了,后半句“有时候排序,甚至无法设定db类型”没懂。

7年前 评论

@wyg27 这个是指 @Ryan 提及的换Mongodb,你后面也说了这个问题啊。DB不能随便换,特别是用了好几年后,优化的积累越来越多。

7年前 评论

@lyhiving

这个是指 @Ryan 提及的换Mongodb,你后面也说了这个问题啊。DB不能随便换,特别是用了好几年后,优化的积累越来越多。

哦,是的。更换 DB 类型这种事,我说不上话。至于优化积累,在我们的小公司里也就那么回事吧,两个字概括就是“呵呵”。现实如此,早已妥协。

7年前 评论

哈哈,楼主我和你遇到了同样的问题,解决方法一样一样的,直接全部text类型,简单粗暴,管他呢!

7年前 评论

@浮夸 含泪与君握手!

7年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!