【mysql建表避坑指南】

mysql建表避坑指南

  • 一、建表阶段的坑与规范
    • [1. 字符集与排序规则](#1. 字符集与排序规则)
      • [1.1 utf8 和 utf8mb4的区别](#1.1 utf8 和 utf8mb4的区别)
      • [1.2 实际影响举例](#1.2 实际影响举例)
    • [2. 存储引擎](#2. 存储引擎)
    • [3. 主键设计](#3. 主键设计)
    • [4. 字段命名](#4. 字段命名)
    • [5. 默认值与 NOT NULL](#5. 默认值与 NOT NULL)
  • 二、字段类型选择的坑与最佳实践
    • [1. 整数类型](#1. 整数类型)
    • [2. 字符串类型](#2. 字符串类型)
    • [3. 时间类型](#3. 时间类型)
    • [4. 浮点数与定点数](#4. 浮点数与定点数)
    • [5. 枚举与集合](#5. 枚举与集合)
  • 三、索引设计的坑与原则
    • [1. 索引不是越多越好](#1. 索引不是越多越好)
    • [2. 联合索引最左前缀原则](#2. 联合索引最左前缀原则)
    • [3. 索引冗余](#3. 索引冗余)
    • [4. 索引失效场景(常见坑)](#4. 索引失效场景(常见坑))
    • [5. 覆盖索引](#5. 覆盖索引)
  • 四、查询性能的坑与优化
    • [1. 切忌 SELECT *](#1. 切忌 SELECT *)
    • [2. 分页深翻页](#2. 分页深翻页)
    • [3. JOIN 滥用](#3. JOIN 滥用)
    • [4. 隐式提交与大事务](#4. 隐式提交与大事务)
    • [5. 慢查询日志与 EXPLAIN](#5. 慢查询日志与 EXPLAIN)
    • [6. 索引下推与 ICP](#6. 索引下推与 ICP)
    • [7. 避免在 WHERE 条件中使用 !=、<>、NOT IN](#7. 避免在 WHERE 条件中使用 !=、<>、NOT IN)
    • [8. 使用 UNION 代替 OR](#8. 使用 UNION 代替 OR)
  • 五、其他容易忽略的坑
    • [1. 数据库连接池设置](#1. 数据库连接池设置)
    • [2. 大字段与行溢出](#2. 大字段与行溢出)
    • [3. 外键约束](#3. 外键约束)
    • [4. 自增主键步长](#4. 自增主键步长)
      • [4.1 变量含义](#4.1 变量含义)
      • 典型使用场景
      • [3. 设置方法](#3. 设置方法)
    • [5. 时区设置](#5. 时区设置)

一、建表阶段的坑与规范

1. 字符集与排序规则

  • 坑:使用 latin1 或默认字符集,导致存储中文乱码;或者混用不同字符集导致索引失效、排序结果异常。

  • 正确做法:统一使用 utf8mb4 字符集,排序规则用 utf8mb4_general_ci 或 utf8mb4_unicode_ci。MySQL 8.0 默认已改为 utf8mb4,但需确认。

  • CREATE TABLE user ( ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

1.1 utf8 和 utf8mb4的区别

  • utf8 和 utf8mb4 是 MySQL 中两种常见的 UTF-8 字符集,它们的核心区别在于支持的 Unicode 字符范围和存储字节数。简单来说:

  • utf8 是 MySQL 中的一个"别名",但实际上它只实现了 UTF-8 的一部分,最多使用 3 个字节存储一个字符,只能支持 Unicode 基本多语言平面(BMP,即码点范围 U+0000 到 U+FFFF)。

  • utf8mb4 是真正的 UTF-8 完整实现,最多使用 4 个字节 存储一个字符,可以支持所有 Unicode 字符,包括 BMP 以外的字符(如 emoji 表情、生僻汉字、一些特殊符号等)。

详细区别

特性 utf8 utf8mb4
最大字节数 3 字节 4 字节
支持的字符范围 Unicode 基本多语言平面(BMP) 全部 Unicode 字符,包括 BMP 和补充平面(如 emoji)
是否支持 emoji ❌ 不支持(会报错或变成乱码) ✅ 完全支持
是否支持生僻汉字(如"𠮷") ❌ 不支持 ✅ 支持
MySQL 引入版本 很早期就有(但实际为 3 字节 UTF-8) MySQL 5.5.3 开始引入
常用排序规则 utf8_general_ci, utf8_unicode_ci utf8mb4_general_ci, utf8mb4_unicode_ci(MySQL 8.0 推荐 utf8mb4_0900_ai_ci)

1.2 实际影响举例

  1. 插入 emoji 表情(如 "😊")
    若表字符集为 utf8,插入 '😊' 会报错:Incorrect string value: '\xF0\x9F\x98\x8A' for column。

若为 utf8mb4,则可以正常存储。

  1. 生僻汉字(如 "𠮷")
    "𠮷" 的 Unicode 码点是 U+20BB7,需要 4 字节编码,utf8 无法存储。

2. 存储引擎

  • 坑:使用 MyISAM(不支持事务、行锁,崩溃恢复差)。

  • 正确做法:一律使用 InnoDB(支持事务、行级锁、外键、崩溃恢复)。

3. 主键设计

  • 坑:使用业务字段作为主键(如身份证号),或使用 UUID 作为主键(导致页分裂、索引膨胀)。

  • 正确做法:使用自增整数主键(BIGINT AUTO_INCREMENT)或雪花算法生成的分布式 ID(有序)。

    自增主键利于写入性能,页分裂少;但分库分表时需改用分布式 ID。

4. 字段命名

  • 坑:使用关键字作为字段名(如 order、group、desc),导致 SQL 必须加反引号,易出错。

  • 正确做法:避免使用 MySQL 保留字,字段名用下划线命名法(如 user_name),表名用复数或单数(团队统一)。

5. 默认值与 NOT NULL

  • 坑:允许字段为 NULL,导致查询时需要用 IS NULL 或 IS NOT NULL,索引效率低,且聚合函数(如 COUNT)会忽略 NULL。

  • 正确做法:尽量设置 NOT NULL,并给一个合理的默认值(如 DEFAULT '' 或 DEFAULT 0)。如果必须存 NULL,需明确业务含义。

二、字段类型选择的坑与最佳实践

1. 整数类型

  • 坑:用 INT 存布尔值(浪费空间);用 INT(1) 误以为限制长度(其实只影响显示宽度)。

  • 正确做法:

    • 布尔值用 TINYINT(1) 或 BOOLEAN(MySQL 中 BOOLEAN 是 TINYINT(1) 的别名)。

    • 根据数值范围选择合适类型:TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT。

    • INT(11) 中的数字不影响存储范围,仅用于一些客户端显示,无实际意义。

2. 字符串类型

  • 坑:滥用 VARCHAR(255) 存储所有字符串,或用 TEXT 存储短文本(TEXT 有单独存储空间,影响排序/索引)。

  • 正确做法:

    • 长度固定且短的用 CHAR(如手机号、MD5值)。

    • 长度不固定但短(<255)用 VARCHAR,但需根据实际最大长度设置,不要随意给 255。

    • 超过 5000 字符考虑 TEXT 或 MEDIUMTEXT,但注意 TEXT 类型不能有默认值,且需前缀索引。

    • 存储 JSON 用 JSON 类型(MySQL 5.7+),可高效查询 JSON 字段。

3. 时间类型

  • 坑:用 VARCHAR 存时间戳,或混淆 DATETIME 与 TIMESTAMP。

  • 正确做法:

    • 日期用 DATE,日期时间用 DATETIME(范围大,不受时区影响)或 TIMESTAMP(受时区影响,2038年问题)。

    • 记录创建时间、更新时间建议用 DATETIME,配合 DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP。

    • 不要用字符串存时间,无法使用时间函数且排序错误。

4. 浮点数与定点数

  • 坑:用 FLOAT/DOUBLE 存金额,导致精度丢失。

  • 正确做法:金额等精确小数用 DECIMAL(如 DECIMAL(10,2))。浮点数只适合科学计算。

5. 枚举与集合

  • 坑:用 ENUM 存可变选项(增加新值需 ALTER 表,锁表风险)。

  • 正确做法:固定不变的选项可用 ENUM(如性别),会变化的选项用关联表(外键)或 TINYINT 映射。

三、索引设计的坑与原则

1. 索引不是越多越好

  • 坑:每个字段都建索引,导致写入慢、占用磁盘空间,且优化器可能选错索引。

  • 正确做法:只为查询条件(WHERE)、排序(ORDER BY)、分组(GROUP BY)、关联(JOIN)字段建索引。写多读少的表索引要精简。

2. 联合索引最左前缀原则

  • 坑:建立索引 (a, b, c),但查询条件只用 b 或 c,索引失效。

  • 正确做法:联合索引按查询频率排序,将最常用、区分度高的列放左边。若查询条件缺失左边列,需另建索引。

3. 索引冗余

  • 坑:已有 (a, b) 索引,又建 (a) 索引,后者完全冗余。

  • 正确做法:定期用 pt-duplicate-key-checker 等工具检查冗余索引并删除。

4. 索引失效场景(常见坑)

  • 对索引列使用函数或计算:WHERE DATE(create_time) = '2023-01-01' → 应改为 create_time >= '2023-01-01' AND create_time < '2023-01-02'。

  • 隐式类型转换:字段为字符串,传入数字,导致全表扫描(如 WHERE phone = 13800001111,phone 是 VARCHAR)。

  • 模糊查询左模糊:LIKE '%abc' 不走索引,右模糊 LIKE 'abc%' 可走。

  • OR 条件:WHERE a = 1 OR b = 2,如果 a 和 b 没有分别建索引,可能全表扫描。可用 UNION 优化或建复合索引(MySQL 5.0+ 索引合并可能利用,但不如 UNION 稳定)。

5. 覆盖索引

  • 优化技巧:查询的字段都在索引中时,只需扫描索引树,无需回表。设计索引时尽量覆盖常用查询。

四、查询性能的坑与优化

1. 切忌 SELECT *

  • 坑:SELECT * 返回所有字段,可能包含大字段(如 TEXT),增加网络开销和内存占用,且无法利用覆盖索引。

  • 正确做法:只查询需要的字段。

2. 分页深翻页

  • 坑:LIMIT 100000, 20 会先扫描 100020 行再丢弃前 100000 行,随着翻页越深越慢。

  • 正确做法:

    • 使用子查询延迟关联:
      SELECT * FROM t WHERE id >= (SELECT id FROM t ORDER BY id LIMIT 100000, 1) LIMIT 20

    • 记录上次最大 ID(或排序字段),下一页用 WHERE id > last_id LIMIT 20。

    • 若用时间排序,确保时间字段有索引,并用 WHERE time > last_time 方式。

3. JOIN 滥用

  • 坑:多表 JOIN 且无索引,导致临时表、文件排序;或者 JOIN 顺序不当。

  • 正确做法:

    • 确保 JOIN 的关联字段有索引(类型一致)。

    • 小表驱动大表(优化器通常会自动选择,但需检查 EXPLAIN)。

    • 避免超过三张表 JOIN,必要时冗余字段或反范式化。

4. 隐式提交与大事务

  • 坑:在循环中逐条 INSERT/UPDATE,导致多次事务提交,性能差且可能锁范围扩大。

  • 正确做法:批量操作(INSERT INTO ... VALUES (...), (...)),或用 START TRANSACTION 包裹批量语句,统一提交。

5. 慢查询日志与 EXPLAIN

  • 坑:从不开启慢查询日志,问题发生后无从排查。

  • 正确做法:

    • 开启慢查询日志,设置 long_query_time = 1(或 0.5 秒),定期分析。

    • 对每条重要查询使用 EXPLAIN 分析执行计划,关注 type(ALL 全表扫描需优化)、rows(扫描行数)、Extra(Using filesort 或 Using temporary 需优化)。

6. 索引下推与 ICP

  • 原理:MySQL 5.6+ 支持索引条件下推(ICP),在存储引擎层过滤数据,减少回表。但前提是联合索引的前缀字段已使用,后置字段可在索引内过滤。

7. 避免在 WHERE 条件中使用 !=、<>、NOT IN

  • 这些操作通常无法利用索引,除非数据分布特殊。可考虑改写为范围查询(如 id < 1 OR id > 1 仍无法走索引),或根据业务排除。

8. 使用 UNION 代替 OR

  • 示例:WHERE a = 1 OR b = 2 可改为 SELECT ... WHERE a = 1 UNION SELECT ... WHERE b = 2,但需确保 a、b 分别有索引。

五、其他容易忽略的坑

1. 数据库连接池设置

  • 坑:连接池太小导致请求排队,太大导致数据库负载高。

  • 正确做法:根据 QPS、事务耗时合理配置(如 HikariCP 的 maximumPoolSize 一般设为 CPU 核心数 * 2 + 磁盘数,实际需压测)。

2. 大字段与行溢出

  • 坑:表中有多个 TEXT/BLOB 字段,且频繁查询,导致性能下降(行溢出,需额外 IO)。

  • 正确做法:将大字段拆分到附属表,主表只存常用字段。

3. 外键约束

  • 坑:在 InnoDB 中使用外键,每次更新/删除都会检查引用完整性,影响性能。

  • 正确做法:由应用层保证数据一致性,数据库层面不设外键,但可建索引提高关联查询性能。

4. 自增主键步长

  • 坑:使用默认步长 1,但分库分表后可能冲突。

  • 正确做法:分布式场景用雪花算法,或设置 auto_increment_increment 和 auto_increment_offset。

    这两个是 MySQL 的系统变量,用于控制 AUTO_INCREMENT 列的增长步长和起始偏移量。它们主要用在多主复制、分库分表或分布式 ID 生成场景中,目的是避免不同节点生成的自增主键发生冲突。

4.1 变量含义

  • auto_increment_increment:控制自增值每次增加的步长(间隔)。默认值为 1。

  • auto_increment_offset:控制自增值的起始偏移量(起点)。默认值为 1。

公式:

自增列的值 = auto_increment_offset + N × auto_increment_increment (N 为插入次数)

典型使用场景

假设你有两台 MySQL 服务器(主-主复制或分片),希望它们生成的自增主键全局唯一,不会重复。可以这样配置:

  • 节点1:auto_increment_offset = 1,auto_increment_increment = 2 → 生成 ID:1, 3, 5, 7...

  • 节点2:auto_increment_offset = 2,auto_increment_increment = 2 → 生成 ID:2, 4, 6, 8...

这样两个节点生成的 ID 交错且唯一。

3. 设置方法

  • 全局设置(需要超级权限):
sql 复制代码
SET GLOBAL auto_increment_increment = 2;
SET GLOBAL auto_increment_offset = 1;
  • 会话级设置(仅当前连接):
sql 复制代码
SET SESSION auto_increment_increment = 2;
SET SESSION auto_increment_offset = 1;
  • 配置文件(my.cnf/my.ini):
sql 复制代码
[mysqld]
auto_increment_increment=2
auto_increment_offset=1

5. 时区设置

  • 坑:应用与数据库时区不一致,导致时间错乱。

  • 正确做法:统一设置为 +08:00,连接参数加 serverTimezone=Asia/Shanghai。

相关推荐
V1ncent Chen2 小时前
从零学SQL 02 MySQL架构介绍
数据库·sql·mysql·架构·数据分析
大母猴啃编程2 小时前
MySQL内置函数
数据库·sql·mysql·adb
@小匠2 小时前
Spring-Gateway-理论知识总结/常问面试题
数据库·spring·gateway
逍遥德2 小时前
postgresql数据库连接问题
数据库·postgresql
此方ls2 小时前
Redis源码研读八——listpack.c 1080-1528行
c语言·数据库·redis
隔壁小邓2 小时前
TIDB分布式数据库
数据库·分布式·tidb
wellc2 小时前
redis连接服务
数据库·redis·bootstrap
隔叶听风2 小时前
RocketMQ 与 Kafka 长轮询详解
数据库·kafka·rocketmq
袋鼠云数栈2 小时前
构建金融级数据防线:数栈 DataAPI 的全生命周期管理实践
java·大数据·数据库·人工智能·api