尧图精选

MySQL数据类型选型实战:从底层存储到索引性能的全面解析

🕒 发布时间:2026/10/2 18:43:37 📁 来源:尧图网络
MySQL 数据类型很多人在学习 MySQL 的时候都会忽略这个基础知识觉得就是几个类型而已背一背就过去了。但实际上我在实际项目中见过太多因为类型选错导致的线上事故一张表存几年数据就膨胀到几十个GB查询慢得离谱字段存手机号用了整型结果前面带0的手机号全丢了日期字段用字符串存排序彻底乱了。这篇文章我会把这几年来在 MySQL 数据类型上踩过的坑、积累的经验一次性讲透从每种类型的底层存储逻辑出发说清楚什么时候该用什么、为什么这么选以及有哪些容易忽略的细节。适合正在系统学习 MySQL 的初学者也适合已经写了好几年 SQL、但从来没认真抠过类型细节的同学。1. 数据类型为什么是 MySQL 学习的重点1.1 数据类型决定存储效率和查询性能很多人学 MySQL 的时候第一反应就是写 SQL、建表、查数据数据类型通常都是翻文档时被动扫一眼然后继续往下学。但以我的经验来说数据类型才是 MySQL 一切行为的根基。因为你建的每一张表、写的每一个查询、加的每一个索引最终都要落到存储引擎那一层去处理而存储引擎的第一道关卡就是数据类型。MySQL 的 InnoDB 存储引擎在磁盘上以页为单位组织数据默认一页 16KB。一个字段的类型选择直接决定了这一页能存多少行。比如你用一个 INT4字节和一个 BIGINT8字节存同一个数值单行数据量在字段多的时候差别可能不大但在海量数据场景下行数上千万、上亿时每行多出来的 4 个字节最终表占用空间、索引占用空间、IO 开销都会成倍放大。这不是理论推导我实际维护过一张 1.2 亿行的日志表把其中两个可以压缩为 SMALLINT 的字段从 INT 改为 SMALLINT 之后表体积直接少了将近 3GB查询热数据的速度也有肉眼可见的提升。性能和数据类型的关系还有另一面函数和隐式转换。MySQL 在比较不同类型的值的时候会做隐式转换如果一个 varchar 字段存储的其实是数字查询时拿它和数值比较MySQL 就会对字段做转换转换之后索引可能就失效了。这类问题在最基础的 SELECT 查询里就能碰到而且是典型的性能事故。另外就是排序。MySQL 的 ORDER BY 排序对不同类型的执行策略不一样。数值类型的排序是最快的字符串类型排序需要走字符集和排序规则日期类型走的是内部数值。如果你把一个日期存成字符串排序结果会按字典序来大概率不是你想要的。这也是为什么我反复强调类型选错了后面的所有操作都要为它买单。1.2 泛泛而学与系统掌握的区别网上讲 MySQL 数据类型的资料很多但大部分是列表式地罗列类型和占用字节数。看完一遍感觉都懂了一到真正建表的时候还是不知道选什么。我自己的体会是数据类型的学习必须带着“为什么要这么设计”的思路去学。比如 VARCHAR 的长度到底该怎么定TEXT 能不能当主键TIMESTAMP 和 DATETIME 差在哪里这些问题单靠背表格是搞不定的需要理解每种类型的本质。还有一个常见误解认为数据类型只是数据库内部的事情和上层代码无关。实际上恰恰相反。你在 Java、Python、C 里写入的数据最终都要映射到 MySQL 类型上。比如 Java 的 Long 对应 BIGINTPython3 的 int 是变长整数映射到 MySQL 时需要根据实际情况选用 BIGINT 或 INT。如果映射错了数据可能溢出、精度丢失甚至直接写入失败。这从侧面解释了为什么 MySQL 数据类型不仅是 DBA 要掌握的内容也是后端开发、数据分析同学必须吃透的基础知识。多说一句Redis 那边的数据类型和 MySQL 完全不是一个思路。Redis 的字符串、列表、集合这些类型更多是服务于内存数据结构MySQL 的类型则既要考虑磁盘存储、事务回滚还要兼容 SQL 标准。学 MySQL 数据类型的时候别拿 Redis 的思维来套否则很容易对 VARCHAR 和 TEXT 的语义产生误解。我在带新人的时候经常说数据类型是“牵一发动全身”的知识点只有系统地学才能在使用时不踩坑。1.3 与事务、索引、存储过程等模块的联动数据类型的错误不会只在建表时报错它会顺着 MySQL 的各个功能模块渗透下去。举几个例子索引这一块B树索引对键值的比较是极其敏感的。前缀索引、联合索引、覆盖索引的优化都依赖于对字段类型的精确理解。比如 VARCHAR(255) 建索引和 VARCHAR(20) 建索引索引体积差别很大在内存有限的情况下前者可能导致索引无法完全放入缓冲池查询性能断崖式下跌。事务这一块Redo log 和 Undo log 在记录数据变更时需要序列化变更前后的值字段类型越复杂日志记录的内容也越多。我之前排查过一个写性能问题就是因为一张表里有几个 JSON 字段且更新频繁导致事务日志膨胀得很快。存储过程方面如果你在存储过程中使用变量变量的类型需要和字段类型精确匹配。MySQL 的存储过程变量如果不声明类型或声明得不对赋值时会发生隐式转换轻则精度损失重则运行时错误。虽然这篇博文讲的是数据类型本身但我们必须清楚它不是一个孤立的知识点而是贯穿 MySQL 学习整个主线的抓手。2. 数值类型选错一个字节都可能出问题2.1 整数类型TINYINT、SMALLINT、MEDIUMINT、INT、BIGINTMySQL 的整数类型一共有五种区别就是存储空间和取值范围。我直接列一个实际选择时最常用的表类型存储字节有符号范围无符号范围适用场景TINYINT1-128~1270~255状态值、布尔值、年龄SMALLINT2-32768~327670~65535小范围计数、城市编码MEDIUMINT3-8388608~83886070~16777215中等范围计数INT4-2147483648~21474836470~4294967295主键、普通编号BIGINT8±92233720368547758070~18446744073709551615雪花ID、交易流水很多人会忽略无符号 unsigned 的使用。比如一个表的自增主键用 INT UNSIGNED 可以多存一倍的数据量如果你确认这个字段永远不会是负数就加上 UNSIGNED。但要注意MySQL 8.0 之后对 unsigned 的处理更严格了和外键关联的时候如果一边是有符号另一边是无符号连接时容易出问题。我遇到过一个坑两个表通过 INT 关联一个表用了 UNSIGNED另一个表没用结果 JOIN 的时候 MySQL 选择了全表扫描因为两边类型不完全匹配索引就没用上。这个问题排查了大半天最后才意识到是 UNSIGNED 引起的。在实际业务中最常见的整数类型错误有三个。第一个是拿 INT 存手机号。手机号是11位INT 最大只能存到21亿多也就是10位左右手机号放进去直接溢出加上前导的 1 还会被舍入数据就废了。手机号这种本身不需要做加减乘除的字段应该用 VARCHAR(11) 或者 CHAR(11) 存储。第二个是用 INT 存布尔值。MySQL 本身没有 BOOLEAN 类型它只是 TINYINT(1) 的别名。很多人建表时写 BOOLEAN看起来挺美实际底层就是 TINYINT。逻辑上没问题但如果你在代码里严格要求返回 true/falseORM 框架可能会在类型转换上多做一层处理有一点性能损耗。比起这个我其实更建议业务上用 TINYINT(1) 配合可读性好的 CHECK 约束直接避免歧义。第三个是溢出不报错。默认的 SQL Mode 下MySQL 对整数溢出会给出 warning 然后截断而不是直接报错。也就是说你往 TINYINT 里写 200它可能存成 127。这种静默错误在线上极难排查所以我建议在配置文件里把 sql_mode 设置为 STRICT_TRANS_TABLESMySQL 8.0 默认就是这样非法值会直接报错不让脏数据进表。2.2 小数类型DECIMAL 才是精确值的正解小数类型这块很多从其他语言转过来的人会直觉地选 FLOAT 或 DOUBLE毕竟是“浮点数”听起来挺合理。但在 MySQL 里FLOAT4字节和 DOUBLE8字节都是近似值存储它们遵循 IEEE 754 标准这意味着你在数据库里看到的 0.1实际存储的可能是 0.100000000000000005551。做财务计算、金额累计的时候这种误差会一步一步积累最后对不上账到那时候再回头改字段类型简直是灾难级操作。DECIMAL 才是精确的小数类型。它把数字以字符串形式保存按精度和小数位数两部分定义DECIMAL(M, D)M 是总位数D 是小数点后的位数。比如 DECIMAL(10, 2) 表示总共10位数字其中2位小数能存的最大值是 99999999.99。M 最大可以到 65D 最大到 30。DECIMAL 的实际存储方式也很有意思每9个数字存4个字节剩余部分另算。比如 DECIMAL(18, 2)整数部分是16位小数部分是2位一共18位。存储时 MySQL 会按每9位一组去分配空间但实际占用往往比大家想象的要多一些。所以 DECIMAL 类型在精确的同时性能比 FLOAT/DOUBLE 差尤其是大量聚合计算时更明显。我的做法是金额、税率、汇率、利息这类绝对要精确的小数用 DECIMAL而经纬度、温度、评分这类对精度要求不高的、只需要近似值的用 DOUBLE 就够了。还有个容易被忽略的细节DECIMAL 在 MySQL 5.0 之后就固定为精确计算但如果你对 DECIMAL 字段做除法得到的仍然是 DECIMAL不过结果的小数位数是按规则扩展的这导致一些报表查询里出现超长小数。解决方法是显式用 ROUND 函数或在 SELECT 里 CAST 到需要的精度别偷懒。2.3 数值类型与排序、索引的关系数值类型在索引和排序上的性能显著优于字符串类型。B树索引对数值的比较是直接按字节大小进行的而字符串还要经过排序规则比较。这就意味着如果一个字段既可以用 INT 也可以用 VARCHAR比如成员等级、省份代码尽量选 INT。哪怕业务逻辑里地方编码是“01”“02”这种带前导零的也别用字符串存 INT 之后在应用层补零就行。排序同理ORDER BY INT 字段比 ORDER BY VARCHAR 字段快得多因为底层比较直接。顺带一提无符号字段有一个非常容易踩的坑如果你在建索引的时候用了 UNSIGNED 字段查询条件里却传入了负数MySQL 会立即报错因为无符号字段不接收负数。同样地两个字段联表的时候一边是 SIGNED、一边是 UNSIGNED查询优化器可能判断无法用索引进行等值连接导致性能骤降。所以在一个库的设计规范里建议把关联字段的类型、符号、长度都统一哪怕看起来有点冗余也要保持一致这个习惯能帮你省下大量排查慢查询的时间。3. 字符串类型CHAR、VARCHAR、TEXT的取舍3.1 CHAR 和 VARCHAR 的本质差异字符串类型是 MySQL 里最容易被乱用的领域。先说 CHAR 和 VARCHAR 的区别这个每个人都知道“定长”和“变长”但真正落实到存储和查询上很多人又分不清。CHAR(M) 是定长字符串M 表示字符数范围是0~255。当写入的数据长度小于 M 时MySQL 会用空格填充到 M 个字符读取时再把尾部空格去掉。VARCHAR(M) 是变长字符串M 也是字符数范围是0~65535受行大小限制它额外用1~2个字节存储实际长度只占用实际需要的空间加长度字节。从存储角度看VARCHAR 对短数据来说是省空间的但 CHAR 也有它的用武之地长度完全固定的字段比如 MD5 值32位、UUID如果用字符串存36位、身份证号18位、银行卡号、状态码、国家缩写等用 CHAR 反而更好。因为 VARCHAR 有额外的长度字节而且定长字符串在行存储时更容易被预测偏移量InnoDB 对这种字段的处理更高效。有一类特别常见的误区是把所有字符串都定义成 VARCHAR(255)。这倒算不上什么致命错误但浪费空间是实打实的。VARCHAR(255) 每个值最多只需用1字节长度标记因为255 256而超过255就需要2字节。更重要的问题是VARCHAR(255) 建索引时如果是 utf8mb4 字符集每个字符最多4字节255个字符的索引前缀最大就是 255*41020 字节已经非常接近 InnoDB 索引列上限767字节的限制了很多时候不得不建前缀索引。所以别图省事统一 VARCHAR(255)按实际业务长度去定。3.2 TEXT 类型虽好用但代价不小MySQL 的 TEXT 系列包括 TINYTEXT255字节、TEXT65KB、MEDIUMTEXT16MB、LONGTEXT4GB。很多人觉得 TEXT 类型方便想存什么就存什么但实际上 TEXT 的使用代价非常大。第一个代价是行内存储 vs 溢出存储。TEXT 字段的数据如果超过一定大小InnoDB 不会把全部数据放在行记录里而是只放一个20字节的指针真正的内容存储到溢出页。这就意味着你查一行数据的时候如果 SELECT 里包含 TEXT 字段存储引擎可能要多一次额外 IO 去读取溢出页。查询性能的影响在行数多了之后非常明显。第二个代价是 TEXT 类型早期不允许有默认值。MySQL 8.0.13 之后虽然允许了但实际开发中还是很少给 TEXT 设默认值的。这就引出一个实际问题如果业务表结构中有些字段只是偶尔用到大文本比如备注、简介建议单独拆出一张扩展表而不是全部塞进主表。我在一个项目里就是这样做的主表只保留关键字段长文本放扩展表按主键关联结果列表页查询快了非常明显。第三个代价是 TEXT 字段不能直接在内存临时表里高效排序。MySQL 在某些场景下会使用内部临时表完成 GROUP BY、ORDER BY、DISTINCT 操作如果临时表里包含 TEXT 类型MySQL 必须使用磁盘临时表。磁盘临时表性能比内存临时表慢几个数量级线上经常出现“某条 SQL 突然要几十秒”的问题查到最后就是内部临时表里有个 TEXT 字段导致的。这类问题尤其隐蔽因为 SQL 本身看起来并不复杂。3.3 字符集与排序规则的选择字符串类型绕不开字符集问题。MySQL 8.0 默认字符集是 utf8mb4这是在 utf8 基础上扩展出的能存4字节 UTF-8 编码的字符集可以覆盖 Emoji 和一些生僻汉字。utf8mb4 的排序规则之一是 utf8mb4_0900_ai_ci它不区分大小写、不区分口音。如果你的业务需要大小写敏感的比较就得换 utf8mb4_bin 或者在字段上指定 COLLATE。字符集对存储空间的影响巨大。同样一个字符串“中国”在 utf8 下占6字节每个汉字3字节在 utf8mb4 下也是6字节但在 latin1 下只占2字节。表面上看起来是小差异但一张表几千万行的时候字符集选择直接决定了索引大小和内存占用。我见过一个项目因为字符集选了 utf8mb4 但实际业务里根本没有 Emoji就把字段级别切换到 utf8mb3也就是 utf8整个库的大小降了将近25%。还有一个非常容易踩的坑建表时没指定字符集MySQL 会继承库级别的默认字符集。如果库是 utf8mb4而连接字符集是 utf8JOIN 两个不同字符集的字段时MySQL 会做隐式转换索引基本失效。这个问题的外在表现是“这条 SQL 以前秒回现在变慢了”很容易让人去优化 SQL实际上根因在字符集不一致。4. 日期时间类型日期字段别用字符串存4.1 DATETIME 和 TIMESTAMP 的底层差异日期时间大概是所有 MySQL 数据类型里最容易被轻看的一个了。很多人建表时随手选 DATETIME因为“反正都能存日期”。但 DATETIME 和 TIMESTAMP 的差异比大多数人想的大得多。DATETIME 占用8字节存储范围是 1000-01-01 00:00:00 到 9999-12-31 23:59:59它是一个纯粹的日期时间值不涉及时区。TIMESTAMP 存储范围是 1970-01-01 00:00:00 到 2038-01-01 03:14:07它存储的是 UTC 时间戳在存取时会根据会话时区做转换。换句话说TIMESTAMP 更适合记录“某个时刻”DATETIME 更适合记录“某个日历时间”。2038年问题在 TIMESTAMP 里是真实存在的。如果一个表用 TIMESTAMP 存日期到2038年就会溢出。虽然现在看起来很远但如果在设计新系统时发现字段需要存超过2038年的日期就不该用 TIMESTAMP。反过来说TIMESTAMP 的优势是占用空间小、且自动处理时区转换。对于很多互联网应用记录创建时间用 TIMESTAMP 配合 DEFAULT CURRENT_TIMESTAMP 很方便但我个人更偏好显式使用 DATETIME 并自行处理时区因为这样在跨时区业务里更可控不会出现“用户在美国看到的时间比实际差了8小时”这种问题。4.2 DATE、TIME 和 YEAR用合适的粒度存数据除了 DATETIME 和 TIMESTAMPMySQL 还有 DATE3字节仅日期、TIME3字节仅时间、YEAR1字节年份。这些类型的价值在于“精准匹配业务需求”。比如一个生日字段只需要日期完全没必要用 DATETIME一个活动开始的时刻如果需要具体到秒可以用 DATETIME如果只需要精确到分钟可以沿用 DATETIME 但进行格式化也可以直接用 DATE 加 TIME 组合但一般为了清晰还是建议 DATETIME。我在实际项目里更常遇到的问题不是选错了类型而是字段精度不够。有些业务要记录毫秒级的变化DATETIME 默认精度只到秒。MySQL 在 5.6.4 之后支持 DATETIME(N) 和 TIMESTAMP(N)N 表示小数秒位数范围0~6。如果你要记录毫秒就用 DATETIME(3)微秒就 DATETIME(6)。注意括号里的位数会影响存储空间DATETIME(6) 比 DATETIME 多占3字节。选择哪一种取决于你的日志是否需要微秒级排序一般的业务用 DATETIME(0) 或 DATETIME 就足够了。4.3 日期类型的常见错误存字符串与排序乱象日期字段最常见的错误就是用字符串存日期。许多开发同学从接口拿到的是一个字符串比如“2024-03-15 14:30:00”懒得转换直接塞进 VARCHAR 字段里。短期看没问题反正也能查、也能显示。但在数据量变大后问题接踵而至排序的时候字符串按字典顺序排而不是按时间顺序排范围查询 BETWEEN 和大于小于号的比较逻辑全乱提取年月日必须用 SUBSTRING 这类字符串函数。索引方面字符串日期字段上建的索引优化器也没法高效利用因为存储引擎在比较时需要先解析日期字符串。我专门踩过这个坑。当时有个报表需求要按天分组统计订单数前台传进来的日期都是字符串我偷懒直接建了 VARCHAR 字段结果一开始数据量小没感觉到几百万行的时候GROUP BY 查询要好几秒。后来把字段改为 DATE 类型并建索引再加上 DATE_FORMAT 函数查询直接从5秒降到0.1秒以内。这个案例后来被我反复拿来当反面教材。日期类型还有一个容易困惑的点MySQL 对非法日期的处理。在非严格模式下写入“2024-02-30”这种不存在的日期MySQL 不会报错而是存成 0000-00-00在报表统计时这些日期会凭空冒出很多怪数。建议打开 STRICT_TRANS_TABLES让非法日期直接报错。如果确实需要容忍脏数据可以在应用层清洗后再入库至少不要带着隐患操作。5. ENUM 和 JSON容易被忽视的进阶类型5.1 ENUM 的利弊与适用边界ENUM 是 MySQL 的枚举类型定义时指定一个可选值的列表比如 ENUM(待支付,已支付,已取消)。它最大的好处是存储紧凑实际存储时用的是索引值每个值占1~2字节比 VARCHAR 省空间得多而且在写入时会校验取值非法值直接报错等于数据库帮你做了一层数据校验。但 ENUM 的坑也不少。第一个坑是改枚举值需要 ALTER TABLE如果在高峰期修改表结构锁表时间可能会很长。第二个坑是底层存储的是索引序号而不是字符串本身这导致如果你要按枚举值排序得到的顺序是定义时的顺序而不是字母顺序。第三个坑更隐蔽在 MySQL 里 ENUM 存的是定义序号比如第一个值是1第二个是2如果你的代码通过框架自动读取表结构生成下拉选项一定要确认拿到的映射关系对得上。我个人的建议是取值特别稳定且数量少比如性别、订单状态、紧急程度可以用 ENUM如果枚举值会经常变化或者业务里存在“待定”“未知”这类需要动态扩展的状态最好还是用 TINYINT 或 VARCHAR 加应用层校验避免频繁 ALTER TABLE 带来的线上风险。5.2 JSON 类型在 MySQL 中的实际使用MySQL 5.7 开始支持原生 JSON 类型8.0 更是提供了丰富的 JSON 函数。JSON 类型的使用场景很多比如存储用户扩展属性、商品动态规格、埋点事件的原始数据等。JSON 类型和 VARCHAR 存 JSON 字符串最大的区别在于它会对 JSON 内容做合法性校验同时提供 JSON_EXTRACT、- 和 - 操作符进行动态查询。更重要的是JSON 列在 MySQL 8.0 里可以建虚拟列索引Generated Column 索引也就是把 JSON 里的某个字段提取出来作为虚拟列并加索引查询性能好了很多。但 JSON 类型也不是万能药。它只是把“乱”藏在了表结构外面查询、更新、统计尤其是涉及到关联的时候性能远不如规范化后的传统列。我在项目里踩过的坑是把一个问卷答案表设计成一个 JSON 字段存所有答案结果按某个答案筛选用户时虽然用了 JSON_EXTRACT但数据量到百万以后查询非常吃力。后来我把高频查询的答案字段单独提取成普通列JSON 只在存储原始数据时用问题迎刃而解。5.3 二进制类型BINARY、VARBINARY、BLOB二进制类型 BINARY、VARBINARY、BLOB 在实际业务中使用频率没上面几位高但也是数据类型学习中必须了解的一部分。BINARY 和 VARBINARY 对应 CHAR 和 VARCHAR但存储的是字节而不是字符。BLOB 系列TINYBLOB、BLOB、MEDIUMBLOB、LONGBLOB对应 TEXT 系列但存储的是二进制数据。如果一个字段存的是图片、文件、加密串需要用 BLOB如果存的是文本用 TEXT。BINARY 类型在比较时按字节比较适合存哈希值、UUID 原始二进制等。需要提醒的是不要把 BLOB 类型和 BASE64 字符串混为一谈。BASE64 字符串虽然是文本形式但本质也是字符串存 VARCHAR 没问题读取后按 BASE64 解码。而 BLOB 类型存取的是原始字节流。如果存入的是压缩后的二进制不要对其进行字符集转换操作否则数据会损坏。我在处理一个图片存储需求时就是因为误把二进制数据存进 TEXT 字段结果存进去再读出来就变成了乱码算是很深刻的一次教训。6. 从建表规范到日常排障的数据类型实战6.1 一个订单表的类型设计实例讲了不少理论我拿一个实际的订单表来演示一下“正确选型”应该是什么样。假设我们要设计一个电商订单表字段包括订单号、用户ID、订单金额、支付时间、订单状态、收货地址备注、买家备注等。下面是我会推荐的设计字段名类型说明idBIGINT UNSIGNED AUTO_INCREMENT主键电商业务并发高INT可能不够order_noVARCHAR(32)业务订单号可能有前后缀字符不适合整型user_idBIGINT UNSIGNED用户ID关联用户表主键类型保持一致total_amountDECIMAL(12,2)订单金额精确到分用DECIMALpayment_timeDATETIME(0)支付时间业务需要时区可控选DATETIMEorder_statusTINYINT UNSIGNED订单状态代码里维护状态机而不是ENUMreceiver_provinceSMALLINT UNSIGNED省份编码用数值类型而不是字符串address_detailVARCHAR(255)详细地址不需要TEXTbuyer_noteTEXT买家备注长文本放扩展表或本表但少查created_atDATETIME(3)创建时间毫秒精度方便排序updated_atDATETIME(3)更新时间同上这个例子想说明的是每个字段的类型选择都对应一种业务语义。订单号为什么不用 INT因为业务订单号可能有字母、下划线等字符而且订单号长度不是固定的10位以内。金额为什么是 DECIMAL(12,2) 而不是 FLOAT因为钱必须精确12位整体数量级足够覆盖大部分订单场景。订单状态为什么用 TINYINT 而不是字符串或 ENUM因为状态会演进TINYINT 加代码层枚举最灵活。创建时间为什么是 DATETIME(3) 而不是 TIMESTAMP因为上线后极大概率要跨时区而且要精确到毫秒排序。6.2 类型转换的隐形陷阱隐式转换与函数包裹写 SQL 的时候类型相关的隐形陷阱非常多。最常见的就是隐式类型转换。比如一个 VARCHAR 字段 vc 存的是“100”查询条件是 WHERE vc 100MySQL 无法保证字符串和整数比较时能用上索引于是可能全表扫描。MySQL 官方文档也明确指出如果查询中涉及类型不一致的比较优化器可能放弃索引。我在一台线上库中排查了一个慢查询就是 JOIN 条件里一边是 INT 一边是 VARCHAR优化器估算成本后选择了全表扫描加了多少索引都没用最后用 CAST 统一了类型问题才解决。另一个坑是用函数包裹字段导致索引失效。比如 WHERE MONTH(create_date) 5 这种写法虽然逻辑上正确但 MySQL 无法使用 create_date 上的索引因为它必须对每一行计算 MONTH 函数。正确做法是写成 create_date 2024-05-01 AND create_date 2024-06-01。如果你必须按月份统计可以在应用层算好日期范围再查。类型转换还有一个非常隐蔽的场景MySQL 在字符串和数值进行比较时会把字符串转换为数值再比较。这意味着一个 VARCHAR 字段存储的“abc”和 0 比较时MySQL 会把“abc”转换为 0结果是 true。这就容易产生逻辑错误。我见过有人排查一个“WHERE status 0”却把全是“无状态”的记录也查出来最后发现是状态字段存的是字符串“无状态”比较时被转成了0。这类问题在数据校验不严的库中尤其高发。6.3 数据类型问题速查表最后整理一份我平时排查问题时常用的速查表覆盖了数据类型相关的典型症状、可能原因和解决办法现象可能原因解决方案查询突然变慢索引不生效WHERE 条件类型不匹配触发隐式转换统一字段类型或使用 CAST 显式转换排序结果不符合预期日期用 VARCHAR 存储改为 DATE/DATETIME 类型数据写入时静默截断sql_mode 未开启严格模式设置 STRICT_TRANS_TABLES联表查询慢或结果异常两个关联字段类型/符号/字符集不一致统一设计规范建表时保持一致表体积增长异常整数类型选型过大或字符集冗余按数据范围选择最小合适类型小数对不上账用了 FLOAT/DOUBLE 存金额改为 DECIMAL2038年问题使用 TIMESTAMP 存远期日期改用 DATETIME大文本拖慢查询TEXT 字段在主表被频繁查询拆扩展表或减少 SELECT TEXT 列JSON 字段查询慢JSON_EXTRACT 高频查询提取虚拟列加索引或规范化编码乱码/比较错误字符集不一致统一库、表、连接字符集为 utf8mb4这里每一条都是我在真实项目中遇到并排查过的坑。数据类型的学问不是背几个类型定义就结束的它要反复在建表、查询、调优中打磨。遇到 SQL 执行慢或者数据莫名其妙不对的时候第一时间检查类型层面的问题往往会比死磕优化器参数更快见效。最后分享一个我自己养成的习惯每次建表前先画一张字段表把每个字段的类型、长度、是否有符号、字符集、是否能为空都写清楚然后自己问一遍“这个类型的选型是不是最小且可行的选择”。很多线上事故在画表阶段就能避免。数据类型这个知识点看着基础实际影响深入到了 MySQL 使用的方方面面这也是我把它视为 MySQL 学习一大重点的根本原因——它牵一发而动全身值得你花时间反复琢磨。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →