Skip to content

MySQL 规范 ​

本文约定 ECShopX Java 后端(MyBatis-Plus、Flyway)使用 MySQL 时的建表、索引与 SQL 编写规范。数据库默认字符集建议使用 utf8mb4,时区 Asia/Shanghai。


一、建表规约 ​

【强制】表达是与否概念的字段,必须使用 is_xxx 命名,类型为 unsigned tinyint ​

  • 1 表示是,0 表示否。
  • 任何字段如果为非负数,必须是 unsigned。
  • 说明:Domain 实体中的 boolean 字段若不加 is 前缀,需在 MyBatis @TableField 或 <resultMap> 中配置 is_xxx → 属性名的映射。数据库侧仍坚持 is_xxx 命名,以明确取值含义与范围。
  • 正例:逻辑删除字段 is_deleted,1 表示删除,0 表示未删除。

【强制】表名、字段名必须使用小写字母或数字 ​

  • 禁止数字开头;禁止两个下划线中间只出现数字。
  • 字段名修改代价大(难以预发布),命名需慎重。
  • 说明:MySQL 在 Windows 下不区分大小写,Linux 下默认区分。库名、表名、字段名均不允许大写字母。
  • 正例:aliyun_admin,rdc_config,level3_name
  • 反例:AliyunAdmin,rdcConfig,level_3_name

【强制】表名不使用复数名词 ​

  • 表名仅表示实体内容,不表示数量;对应 Domain 类名也为单数。

【强制】禁用保留字 ​

【强制】索引命名 ​

类型命名
主键pk_字段名
唯一uk_字段名
普通idx_字段名

pk_ = primary key;uk_ = unique key;idx_ = index。

【强制】小数类型使用 decimal,禁止 float 和 double ​

  • 存储与比较时 float/double 存在精度损失。
  • 超出 decimal 范围时,建议拆成整数与小数分开存储。

【强制】长度几乎相等的字符串使用 char ​

【强制】varchar 长度不要超过 5000 ​

  • 超过则定义为 text,独立成表,用主键关联,避免影响其它字段索引效率。

【强制】表必备三字段:id、create_time、update_time ​

  • id 必为主键,类型 bigint unsigned;单表自增、步长 1。
  • create_time、update_time 均为 datetime:前者表示主动创建,后者表示被动更新。

【推荐】表命名遵循「业务名称_表的作用」 ​

  • 正例:alipay_task / force_project / trade_config

【推荐】库名与应用名称尽量一致 ​

【推荐】修改字段含义或追加状态时,及时更新字段注释 ​

【推荐】字段允许适当冗余以提高查询性能 ​

须考虑数据一致,冗余字段应满足:

  • a. 不是频繁修改的字段

  • b. 不是唯一索引的字段

  • c. 不是超长 varchar,更不能是 text

  • 正例:各业务线冗余存储商品名称,避免查询时再调外部服务。

【推荐】分库分表阈值 ​

单表行数超过 500 万 或单表容量超过 2GB 时再考虑分库分表。若三年内达不到该量级,建表时不要提前分库分表。

【参考】字符类型与存储长度 ​

合适的字符存储长度可节约空间与索引,并提升检索速度。无符号类型可避免误存负数并扩大表示范围。

对象年龄/时间区间类型字节表示范围
人150 岁以内tinyint unsigned10 ~ 255
龟数百岁smallint unsigned20 ~ 65535
恐龙化石数千万年int unsigned40 ~ 约 43 亿
太阳约 50 亿年bigint unsigned80 ~ 约 10^19

二、索引规约 ​

【强制】业务上具有唯一特性的字段必须建唯一索引 ​

  • 即使是组合字段也要建唯一索引。
  • 应用层校验再完善,没有唯一索引仍可能产生脏数据(墨菲定律)。

【强制】超过三个表禁止 join ​

  • 需要 join 的字段数据类型必须绝对一致。
  • 多表关联时被关联字段必须有索引。
  • 双表 join 也需注意索引与 SQL 性能。

【强制】varchar 上建索引必须指定索引长度 ​

  • 不必对全字段建索引;按文本区分度决定长度。
  • 一般字符串长度为 20 的索引,区分度可达 90% 以上。
  • 可用 count(distinct left(列名, 索引长度)) / count(*) 估算区分度。

【强制】页面搜索严禁左模糊或全模糊 ​

  • 需要模糊搜索请走搜索引擎。
  • 索引具有 B-Tree 最左前缀特性,左值未确定则无法使用该索引。

【推荐】order by 利用索引有序性 ​

  • order by 字段放在组合索引最后,避免 filesort。
  • 正例:where a=? and b=? order by c,索引 a_b_c
  • 反例:where a>10 order by b,索引 a_b 无法排序。

【推荐】利用覆盖索引查询,避免回表 ​

  • 正例:explain 的 extra 出现 using index 即覆盖索引效果。

【推荐】延迟关联或子查询优化超大分页 ​

  • MySQL 会取 offset+N 行再丢弃前 offset 行;offset 很大时效率极低。
  • 控制总页数,或对超大页改写 SQL。
  • 正例:
sql
SELECT t1.*
FROM 表1 AS t1,
     (SELECT id FROM 表1 WHERE 条件 LIMIT 100000, 20) AS t2
WHERE t1.id = t2.id;

【推荐】SQL 性能优化目标 ​

至少 range,要求 ref,最好 const:

  • const:单表最多一行匹配(主键或唯一索引),优化阶段即可读到数据。
  • ref:使用普通索引(normal index)。
  • range:索引范围扫描。

反例:type=index 为索引全扫描,比 range 还慢,接近全表扫描。

【推荐】组合索引:区分度最高的列在最左 ​

  • 正例:where a=? and b=? 且 a 接近唯一,单建 idx_a 即可。
  • 存在非等号与等号混合时,等号列前置。如 where c>? and d=?,即使 c 区分度更高,也应建 idx_d_c。

【推荐】避免隐式类型转换导致索引失效 ​

【参考】创建索引的常见误区 ​

  1. 索引宁滥勿缺——认为每个查询都要建索引。
  2. 吝啬索引——认为索引拖慢写入。
  3. 抵制唯一索引——一律在应用层「先查后插」。

三、SQL 语句 ​

【强制】统计行数使用 count(*) ​

  • 不要用 count(列名) 或 count(常量) 替代。
  • count(*) 是 SQL92 标准,与 NULL 无关;count(列名) 不统计该列为 NULL 的行。

【强制】count(distinct col) 语义 ​

  • 统计该列除 NULL 外的不重复行数。
  • count(distinct col1, col2) 中若一列全为 NULL,即使另一列不同也返回 0。

【强制】sum(col) 注意 NPE ​

  • 列全为 NULL 时 count(col) 为 0,但 sum(col) 为 NULL。
  • 正例:SELECT IFNULL(SUM(column), 0) FROM table;

【强制】判断 NULL 使用 ISNULL() 或 IS NULL ​

  • NULL 与任何值比较结果均为 NULL,不是 true/false。
  • NULL <> NULL、NULL = NULL、NULL <> 1 结果均为 NULL。
  • 反例:select * from t where column1 is null and column3 is not null 在 null 前换行影响可读性;ISNULL(column) 更简洁,执行效率通常更好。

【强制】分页查询时若 count 为 0 应直接返回 ​

  • 避免执行后续分页语句。

【强制】不得使用外键与级联 ​

  • 外键与级联更新适用于单机低并发,不适合分布式、高并发;级联更新强阻塞,有更新风暴风险;外键影响插入速度。关联逻辑在应用层实现。

【强制】禁止使用存储过程 ​

  • 难以调试、扩展与移植。

【强制】数据订正(删除/修改)前先 select ​

  • 确认无误再执行更新,避免误删。

【强制】多表查询/变更时对列名加表别名限定 ​

  • 正例:SELECT t1.name FROM table_first AS t1, table_second AS t2 WHERE t1.id = t2.id;
  • 反例:多表关联未限定表名,后续某表新增同名列会导致 Column 'name' in field list is ambiguous。

【推荐】表别名使用 AS,命名为 t1、t2、t3… ​

【推荐】in 操作能避免则避免 ​

  • 若无法避免,集合元素控制在 1000 个以内。

【参考】字符集与计数 ​

国际化场景使用 utf8mb4。注意 LENGTH 与 CHARACTER_LENGTH 区别:

sql
SELECT LENGTH('轻松工作');           -- 12(字节)
SELECT CHARACTER_LENGTH('轻松工作'); -- 4(字符)