ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

为什么数据库中的255限制是个大坑

为什么数据库中的255限制是个大坑 重新理解这个问题几个月之前的时候, 后端工程师提交了一个PR, 涉及触动了我们这里几乎每一个文件, 差异情况非常巨大, 然而描述却是十分简短, 仅仅写着把字符串列标准化限定为text句号, 我的初次反应是产生了怀疑, 括号255显示其散落位置到处都是, 比如那些、、、slugs, 多年以来使用一切都是正常的状态, 那么为什么要去动压根没坏的东西呢问号。于是呢, 每次队友提出级变更时, 通常情况下我都会做一件事, 这事儿就是试图去证明他的观点是错误的, 这次我也这么做了, 然而结果是我没办法做到, 我做不到去证明他错。后来我发现了一些东西, 这些发现重新塑造了我对于字符串列的看法, 而且这个看法和网上大多数关于“vs text”的文章所讲述的内容不是同一个故事, 不是那样的情况。性能迷思搜这个话题, 会找到十几篇文章, 这些文章承诺巨大性能提升, 几乎都没测, 很多建议是从MySQL继承的, 在MySQL那里和text真的存储和索引不同。并非如此这般: (n)与text运用完全一样的底层存储类型, 嗯, 是可变长结构附带长度头的那种, 具备可选压缩特性, 且为大值。不存在分离的“text存储引擎”这一情况哟。你进行声明(255), 如同text那般进行存储作业, 接着再增添一项长度检查操作。那个长度方面的检查, 就是全部的差异所在。每一次, 或者是(n)列, 去运行约束检查, 以此来确保字符串不会超过n个字符。这是纳秒级别的便宜, 然而并非免费, 并且它发生在热写路径上。在数据库里, 我运行了粗略的基准测试, 将五十万行数据插入到两个几乎一样的表中, 其中一个表的数据类型为255, 另一个表的数据类型是text。进行插入操作, 插入数量为50万行, 单列且无索引, 其中一种情况耗时(255个单位)为4.82秒, 另一种文本情况耗时4.71秒。带3列复合索引6.15s vs 6.02s。约百分之二到百分之三的情况插入速度更快, 在批量加载之外是察觉不到这种情况的。读性能在统计方面是相同的, 约束仅仅在写入的时候才会触发。存储大小按照字节来算都是一样的, 这是因为处于同一种磁盘表示范畴。就只是性能赢属于边际范畴, 那为何还要付出行动去做呢? 原因在于(n)的实际成本从来都不会在基准当中现身, ——它会现身于其中。真正的痛不符合现实的约束某处, “255”演变成了迷信, 并非决策, 没人能够说清楚其中缘由, 用户个人信息限制为255字符, 产品描述字段以及用户名同样有此限制, 仅仅是两年前某人设定的默认值, 所有人都原样复制沿用了。问题在于, 任意的限制最终会演变成真正的限制我们起码遭遇了三次事故, 2023年存在合理的长度约束, 从2025年起要拒绝合法数据, 比如客服粘贴更长的地址, 合作伙伴集成发送更长的SKU, RTL脚本用户有异常长的显示名, 每一次都迫切需要进行ALTER TABLE... TYPE (500)。有那么一个, 听起来没什么危害的, 然而, 要是不只是单纯增加长度的话, 那就得重新把整个表给写一遍一一就算是这样, 在大表上去验证变更的时候, 也会有那么一小会儿拿走锁。对于有着4000万行记录的表的业务时间来说, 可绝不是一件能轻松对待的事儿。切text不将拿掉强制长度的能力移除——仅仅是把决定转移到应该所在的地方: 明确清楚的CHECK约束, 特意去应用, NOT VALID创建的时候不锁定表, 之后单独也不阻碍写操作。如今限制是带有意图、被命名的, 不用对整个表进行重新书写就能够修改: 要是想要提升就drop掉然后重新加上check比ALTER TYPE要便宜许多。ORM层真正咬我们的地方通过这一步, 将那从“好主意”起始的改动推进到“这周就做”的阶段。我们当中有多数的内部服务运用GORM, 它是依据Go的tag来推断列的类型情况。长达多年的模型呈现出这样的样子:gorm:type:(255);每个字符串字段默认设有(255), 缘由是半个教程均如此撰写。问题在于 , GORM的会于tag改变之际乐意收缩列 , 静默生成tag里最后所写的任意长度的 , 包含新服务复制旧模型的情形。我们有个初级工程师意外碰触了环境的notes字段 , 将(255) tag复制到本该放置自由文本的字段之上 , GORM的毫无怨言地运行了。切换以后, 模型就变为了type:text, 长度验证被移出, 进入应用层, ()的状态下, 对于进行书写的人是可见的。utf8.(u.Bio) 1000这修出了一类微妙的bug, 的(n)数字符并非数字节, 多字节UTF - 8输入, 我们Umrah app用户大量书写阿拉伯语或带变音符号的印尼语时, 在Go验证假设与实际强制之间行为不一致。将规则集中于Go, 用utf8验证, 逻辑和错误消息同源, 而非Go接受字符串后, 再用晦涩的value too long for type (255)拒绝。因为sqlc的服务改动更为机械, text列反正生成plain, 然而不用在每次产品需求发生变化之际, 在三处, 也就是文件、sqlc查询以及手动验证那里记着去bump长度。我们没改什么特别说明一下, 我们并没有将每一个约束都去除掉。对于固定格式的字段, 也就是codes、codes, 还有那些当成字符串存放的enums, 要保持着(n)或者转移到真正的enum那里, 这是因为那些相关长度是切实被域所固定住的, 并非是随心所欲的那种情况。落地的规则是十分简单的, 长度方面的限制体现的是真实世界的约束, 比如ISO code、固定ID格式这种情况, 这种情况下要保持或者使用enum而限制仅仅是当时感觉是安全的这种状况就要用text, 要是存在需要限制的情况就在应用代码那里进行验证。我的发现最认可几个判断里n仅仅是text加上长度检查, 存在这样的情况, 同一存储, 处于同样的磁盘表示状态, 会逐字节相同 , 2—3%的写差属于检查开销, 读性能在统计方面呈现零差异 , 不要因为性能去切换。任由约束着的安静税可是极为昂贵之物, 在形成255倍迷信之后, 每当合法数据出现越界情况, 便等同于紧急状况、有极大可能需进行整表重写, 以及业务时间被锁定。约束应当是一种决策呀, 绝非是默认的那种情形。安全进行改约束的方式是CHECK NOT VALID方式, 这种方式创建时不锁表, 单独存在时不阻塞写操作, 其具有命名可改、有特定意图且能够更改的特点, 相比ALTER TYPE便宜一个量级。数字符, 并非字节, 这是隐藏的坑, 在多字节UTF - 8情况下, Go验证与DB强制呈现不一致, 验证集中于应用层同源之后, 错误消息与逻辑源自同一个地方, 有标点。判断: 上面不该期望切到text去修复慢查询, 不会的, 差异最多处于边缘地带。它修复的是随便约束下产生的那种隐匿负担, 像紧急情况跟锁表情况, 还有ORM情况、应用验证情况以及数据库强制情况之间出现的不匹配状况。对于基准赢, 我们不是为此做得, 而是在六个月之后不会有人想要去解释, 为何对于合法名260字符的客户, 其上限却是255。规则是, 域固定长度需用/enum, 而若感觉安全则要用text加上应用层验证。我的建议是, 先去查看表里面究竟有多少个“255”是从别人那里复制过来的默认情况, 要是没人能够讲出那个数字的缘由, 那么它就应该设定为text并加以命名CHECK, 这并非是关于性能方面的事情, 而是为了让你不要再在半夜的时候, 针对260字符的客户名去做紧急处理。
返回列表