一、数据库命名规范
1、通用规则
(1)字符与格式
- 仅使用小写字母、数字、下划线(_) ,禁止使用大写(MySQL 在 Linux 下大小写敏感,统一小写避免歧义)、特殊字符(@、#、$ 等)、空格;
- 命名必须以小写字母开头,禁止以数字 / 下划线开头(避免与 MySQL 内置函数 / 关键字冲突);
- 长度控制:库 / 表名≤32 字符,字段名≤64 字符(适配 MySQL 标识符长度限制),避免过长(如
user_order_2025_history而非user_order_in_2025_year_payment_history)。
(2)语义化原则
- 命名必须 "见名知意",使用业务术语(而非拼音 / 英文缩写,除非是行业通用缩写);
- 禁止拼音 + 英文混用(如
yonghu_info❌,user_info✅),禁止无意义缩写(如u_i❌,user_info✅),通用缩写需统一(如id= 标识、num= 数量、cnt= 计数、addr= 地址)。
(3)关键字规避
- 禁止使用 MySQL 保留字(如
user、order、date、table等),若必须使用需用反引号(`)包裹(不推荐); - 示例:
order表改为order_info,user表改为user_base。
(4)下划线分隔
- 多单词命名用下划线分隔(蛇形命名法),禁止驼峰(如
userInfo❌,user_info✅)。
2、具体规范
(1)数据库(Schema)命名
- 格式:
业务域_模块名[_环境标识] - 说明:
- 业务域:核心业务模块(如
mall= 电商、erp= 企业资源、log= 日志); - 模块名:细分功能(如
order= 订单、user= 用户、pay= 支付); - 环境标识(可选):测试 / 预发环境添加(如
_test、_pre),生产环境不添加;
- 业务域:核心业务模块(如
- 示例:
- 生产:
mall_order(电商订单库)、erp_inventory(ERP 库存库); - 测试:
mall_order_test、erp_inventory_pre。
- 生产:
(2)数据表命名
1)普通业务表
- 格式:
业务模块_表含义[_扩展标识] - 扩展标识:区分同类型表(如
_history= 历史表、_backup= 备份表、_temp= 临时表); - 示例:
user_base(用户基础表)、order_main(订单主表)、order_item(订单项表);user_login_history(用户登录历史表)、order_temp(订单临时表)。
2)分区表
- 格式:在普通表名后加分区维度标识(如
_time= 按时间分区、_hash= 按哈希分区); - 示例:
order_main_time(订单主表 - 按时间分区)、user_order_hash(用户订单表 - 按用户 ID 哈希分区)。
3)中间表 / 临时表
- 临时表(会话级):
tmp_业务模块_用途(如tmp_order_stat= 订单统计临时表); - ETL 中间表:
mid_业务模块_用途(如mid_user_behavior= 用户行为中间表); - 注意:临时表使用后必须及时删除,避免占用资源。
(3)字段命名
1)通用字段
-
主键:统一用
id(单主键),复合主键需语义化(如user_order_id= 用户订单复合主键); -
通用关键字段(全表统一):
字段用途 命名 类型建议 创建时间 create_timeDATETIME/DATETIME(3) 更新时间 update_timeDATETIME/DATETIME(3) 创建人 create_byVARCHAR/INT(关联用户 ID) 更新人 update_byVARCHAR/INT(关联用户 ID) 状态 statusTINYINT(枚举值,需注释说明) 逻辑删除标识 is_deletedTINYINT(0 = 未删,1 = 已删) -
示例:
order_id(订单 ID)、user_mobile(用户手机号)、pay_amount(支付金额)。
2)关联字段
- 格式:
关联表名_关联字段名(避免歧义); - 示例:用户表关联订单表,订单表中用户字段为
user_id(而非uid),商品表中分类字段为category_id(而非cid)。
3)字段注释
-
所有字段必须加注释(尤其是状态字段),示例: sql
ALTER TABLE order_main MODIFY COLUMN status TINYINT NOT NULL COMMENT '订单状态:1-待支付,2-已支付,3-已取消,4-已完成';
(4)索引命名
- 格式:
索引类型_表名_字段名(字段名多则取前 3 个,用下划线分隔); - 索引类型标识:
- 主键索引:
pk_(Primary Key); - 唯一索引:
uk_(Unique Key); - 普通索引:
idx_(Index); - 全文索引:
ft_idx_(Fulltext Index);
- 主键索引:
- 示例:
- 主键:
pk_order_main_id(订单主表 ID 主键); - 唯一索引:
uk_user_mobile(用户表手机号唯一索引); - 普通索引:
idx_order_main_create_time(订单主表创建时间索引); - 联合索引:
idx_order_main_user_id_create_time(订单主表 - 用户 ID + 创建时间)。
- 主键:
(5)视图 / 存储过程 / 函数命名
1)视图(View)
- 格式:
v_业务模块_视图用途; - 示例:
v_order_user(订单 - 用户关联视图)、v_user_pay_stat(用户支付统计视图)。
2)存储过程(Procedure)
- 格式:
proc_业务模块_功能; - 示例:
proc_order_calculate(订单金额计算存储过程)、proc_user_sync(用户数据同步存储过程)。
3)函数(Function)
- 格式:
func_功能描述; - 示例:
func_format_mobile(手机号格式化函数)、func_calculate_age(年龄计算函数)。
(6)触发器 / 事件命名
1)触发器(Trigger)
- 格式:
trg_表名_触发时机_触发动作; - 触发时机:
before/after; - 触发动作:
insert/update/delete; - 示例:
trg_order_main_after_insert(订单主表插入后触发器)。
2)事件(Event)
- 格式:
evt_业务模块_事件用途; - 示例:
evt_order_clean_expire(清理过期订单事件)、evt_user_stat_cron(用户统计定时事件)。
二、数据库基本设计规范
1、设计前:需求与模型规范
(1)需求分析与边界定义
- 明确业务核心实体:梳理核心业务对象(如用户、订单、商品)及实体间关系(一对一、一对多、多对多),避免冗余实体;
- 定义数据生命周期:明确数据的留存策略(如订单数据保留 3 年、日志数据保留 6 个月),为后续分区 / 归档做准备;
- 确认性能指标:明确 QPS、读写比例、数据量预估(如单表峰值数据量、年增长数据量),提前规划分库分表 / 索引策略。
QPS = 系统每秒能够处理的请求(Query / Request)数量
(2)ER 模型设计规范
- 实体命名:与后续库表命名保持一致(见「数据库命名规范」),避免抽象命名(如 "信息表",应使用"用户基础表");
- 关系表达:
- 一对一:优先合并表(如用户基础表 + 用户认证表,仅拆分大字段 / 低频字段);
- 一对多:通过外键(或业务层)关联(如订单主表→订单项表,订单项表加
order_id); - 多对多:新增中间关联表(如用户 - 角色,新增
user_role表,含user_id+role_id);
- 字段粒度:原子化设计(不可再拆分),如地址拆分为
province、city、district、detail_addr,避免存储 "XX 省 XX 市 XX 区 XX 路" 整体字符串。
2、库表结构设计核心规范
(1)数据库选型与引擎
- 优先选择 InnoDB:支持事务、行锁、崩溃恢复、外键(按需使用),MyISAM 仅用于只读 / 日志类场景;
- 字符集与排序规则:
- 库 / 表统一用
utf8mb4(兼容 emoji、特殊字符,避免乱码); - 排序规则选
utf8mb4_general_ci(通用不区分大小写,性能优),需精准区分大小写用utf8mb4_bin;
- 库 / 表统一用
- 避免多数据库混用字符集:如部分库
utf8、部分utf8mb4,导致关联查询乱码。
(2)表结构设计黄金法则
1)主键设计
- 优先用自增主键(
BIGINT UNSIGNED AUTO_INCREMENT):InnoDB 聚簇索引,避免 UUID(离散写入导致索引碎片); - 复合主键场景:仅用于多对多关联表(如
user_role表主键(user_id, role_id)),禁止单表滥用复合主键; - 主键值禁止修改:主键是数据唯一标识,修改会导致关联关系失效。
2)分表策略(提前规划)
- 单表阈值:MySQL 单表数据量建议≤1000 万行,超过则规划拆分;
- 拆分方式:
- 垂直拆分:按字段热度拆分(高频字段→主表,即常用的存放在一个表中,低频 / 大字段→子表);
- 水平拆分:按时间(如订单表按年 / 月拆分)、哈希(如用户表按用户 ID 取模)、范围(如按订单 ID 区间)拆分;
- 分区表替代:数据量 500 万 - 1000 万可先用分区表(如按时间分区),延迟分表复杂度。
(3)约束设计:保障数据一致性
1)主键约束
- 单表必须有主键:无主键的 InnoDB 表会隐式生成主键,导致性能下降;
- 主键字段禁止为空:
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY。
2)唯一约束
- 业务唯一字段必须加唯一索引 / 约束:如手机号、订单编号,避免重复数据;
- 示例:
UNIQUE KEY uk_user_mobile (mobile)。
3)外键约束(谨慎使用)
- 核心业务表:可加外键约束(如订单表
user_id关联用户表id),保障数据引用完整性; - 高并发表:禁用外键(外键会增加写入开销),业务层控制关联关系;
- 禁用级联操作:
ON DELETE CASCADE/ON UPDATE CASCADE易导致批量数据删除 / 更新,引发性能问题。
3、SQL 与性能设计规范
(1)读写设计
- 读优化:
- 避免
SELECT *,仅查询需要的字段; - 分页查询必须加
LIMIT,且用主键 / 索引字段分页(如WHERE id > 1000 LIMIT 100,而非LIMIT 10000, 100);
- 避免
- 写优化:
- 批量操作替代循环单条(如批量 INSERT/UPDATE);
- 大事务拆分:单事务操作行数≤1000,避免锁等待 / 回滚开销。
(2)避免性能陷阱
- 禁止大表全表扫描:如
SELECT * FROM order_main WHERE status=1(无索引); - 禁用
SELECT COUNT(*)统计大表行数:用缓存 / 中间表 / 近似计数(如EXPLAIN预估行数); - 避免临时表 / 文件排序:
ORDER BY/GROUP BY字段必须命中索引。
4、安全与可维护性规范
(1)数据安全
- 敏感数据加密:手机号、身份证、银行卡等字段用脱敏 / 加密存储(如手机号存
138****8000,核心字段用 AES 加密); - 逻辑删除替代物理删除:通过
is_deleted字段标记删除,避免数据丢失,便于恢复; - 权限最小化:数据库账号按业务分配权限(如只读账号、仅增删改账号),禁止使用 root 账号连接业务。
(2)可维护性
-
注释强制:库、表、字段、索引必须加注释,说明业务含义;示例:
CREATE TABLE user_base ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID', mobile VARCHAR(11) NOT NULL DEFAULT '' COMMENT '用户手机号', PRIMARY KEY pk_user_base_id (id), UNIQUE KEY uk_user_base_mobile (mobile) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户基础表'; -
版本控制:数据库结构变更(DDL)纳入版本管理,记录变更内容、执行人、时间;
-
备份策略:核心库每日全量备份 + 增量备份,测试库按需备份,备份后验证可恢复性。
5、设计评审与落地规范
(1)评审要点
- 表结构:字段类型是否合理、通用字段是否齐全、主键 / 索引是否合规;
- 性能:是否存在全表扫描风险、索引是否冗余、大字段是否拆分;
- 一致性:约束是否完善、关联关系是否清晰、数据生命周期是否明确;
- 扩展性:是否预留字段(如
ext1/ext2,仅用于临时扩展,避免频繁 DDL)、是否适配未来分库分表。
(2)灰度落地
- 新表上线:先在测试环境验证性能(如模拟数据量、压测 QPS),再灰度到预发环境;
- 禁止在线上做数据库压力测试;
- 禁止从开发环境、测试环境直接连接生产环境数据库;
- DDL 变更:大表 DDL(如加索引、改字段)选择低峰期执行,MySQL 8.0 + 用
ALGORITHM=INPLACE减少锁表时间。
6、反模式(禁止做法)
- 用 VARCHAR 存储数字(如手机号、订单 ID),导致索引失效 / 排序异常;
- 单表字段过多(>50 个),未做垂直拆分;
- 滥用 ENUM 类型(ENUM 修改需 DDL,不如 TINYINT 灵活);
- 大事务操作(如一次性插入 10 万行数据),导致锁等待 / 回滚;
- 无注释的表 / 字段,新人维护成本高;
- 用 UUID 作为主键(InnoDB 聚簇索引离散写入,性能下降)。
三、字段设计规范
1、通用基础规则(所有字段必遵循)
(1)命名规范(与库表命名统一)
- 仅用小写字母、数字、下划线(_) ,禁止大写、特殊字符、空格,以字母开头;
- 多单词用下划线分隔(蛇形命名),禁止驼峰(如
userName❌,user_name✅); - 见名知意:使用业务术语,禁止拼音 / 无意义缩写(如
yh_mobile❌,user_mobile✅;u_id❌,user_id✅); - 通用缩写统一:仅使用行业通用缩写(如
id= 标识、num= 数量、cnt= 计数、addr= 地址、amt= 金额)。
(2)数据类型选择原则
- 最小化 :选择 "够用且最小" 的类型,减少存储和 IO 开销(如年龄用
TINYINT而非INT); - 精准化 :避免精度丢失 / 类型转换(如金额用
DECIMAL而非FLOAT,时间用DATETIME而非VARCHAR); - 兼容性 :优先选择 MySQL 全版本支持的类型,避免小众类型(如
JSON仅 MySQL 5.7 + 支持,需兼容低版本则用VARCHAR)。
(3)约束与默认值
- 禁止 NULL 值 :所有字段默认设为
NOT NULL,NULL 值会增加索引开销、导致查询条件复杂(如WHERE mobile IS NOT NULL);- 空值替代方案:字符串用空串(
'')、数字用0、布尔用0/1;
- 空值替代方案:字符串用空串(
- 默认值合理 :根据业务场景设置默认值,避免无意义默认(如
status TINYINT NOT NULL DEFAULT 1,而非默认NULL); - 避免过度约束:仅对核心业务字段加唯一 / 外键约束,非核心字段由业务层控制。
(4)注释强制要求
-
所有字段必须加注释,明确业务含义、枚举值说明、取值范围;
-
示例:
status TINYINT NOT NULL DEFAULT 1 COMMENT '订单状态:1-待支付,2-已支付,3-已取消,4-已完成', user_mobile VARCHAR(11) NOT NULL DEFAULT '' COMMENT '用户手机号,仅支持11位中国大陆手机号'
2、核心字段类型选型规范(MySQL 适配)
(1)数值型字段
| 类型 | 占用空间 | 适用场景 | 注意事项 |
|---|---|---|---|
| TINYINT | 1 字节 | 枚举值(状态、类型)、年龄 | 取值范围 - 128~127(无符号 0~255),优先用无符号(UNSIGNED) |
| SMALLINT | 2 字节 | 数量较少的数值(如商品分类 ID、地区编码) | 取值范围 - 32768~32767(无符号 0~65535) |
| INT | 4 字节 | 常规 ID(如用户 ID、订单 ID)、数量 | 取值范围 - 2^31~2^31-1(无符号 0~2^32-1),单表数据量≤4000 万优先用 |
| BIGINT | 8 字节 | 大表 ID、分布式 ID、大数统计 | 取值范围 - 2^63~2^63-1(无符号 0~2^64-1),单表数据量 > 4000 万必用 |
| DECIMAL(M,D) | 按精度定 | 金额、费率、百分比 | M = 总位数,D = 小数位数(如金额用DECIMAL(10,2),费率用DECIMAL(5,4));禁止用 FLOAT/DOUBLE(精度丢失) |
数值型设计禁忌
- 禁止用
VARCHAR存储数值(如手机号、订单 ID、身份证号):导致索引失效、排序异常、查询条件复杂; - 禁止用
INT存储手机号:手机号超过 INT 取值范围(最大 2147483647),需用CHAR(11); - 无符号数值加
UNSIGNED:如id BIGINT UNSIGNED,扩展取值范围。
(2)字符串型字段
| 类型 | 占用空间 | 适用场景 | 注意事项 |
|---|---|---|---|
| CHAR(N) | 固定 N 字节 | 长度固定的字符串(手机号、身份证号、订单编号) | N≤255,如手机号CHAR(11)、订单编号CHAR(32);查询性能优于 VARCHAR |
| VARCHAR(N) | 可变(1~N+1 字节) | 长度可变的字符串(姓名、地址、备注) | N 按实际最大长度设置(如姓名VARCHAR(32)、地址VARCHAR(255));避免设为VARCHAR(255)(浪费空间) |
| TEXT | 最大 65535 字节 | 长文本(如商品描述、用户简介) | 拆分子表存储(避免主表查询性能下降);禁止用 TEXT 存储可拆分的结构化数据 |
| LONGTEXT | 最大 4GB | 超大文本(如富文本内容、日志) | 仅用于非核心字段,且必须拆分子表 |
字符串型设计禁忌
- 禁止用
TEXT存储短字符串(如备注≤200 字用VARCHAR(200)); - 禁止用
VARCHAR存储时间 / 日期(如VARCHAR(20)存2025-01-01); - 字符串字段加默认值:如
user_name VARCHAR(32) NOT NULL DEFAULT ''。
(3)时间 / 日期型字段
| 类型 | 占用空间 | 适用场景 | 注意事项 |
|---|---|---|---|
| DATETIME | 8 字节 | 业务时间(创建时间、更新时间) | 无时区依赖,精准到秒;MySQL 5.6 + 支持DATETIME(3)(毫秒级),优先使用 |
| TIMESTAMP | 4 字节 | 需时区适配的时间(如跨境业务) | 取值范围 1970~2038,受时区影响;自动更新场景可用(如ON UPDATE CURRENT_TIMESTAMP) |
| DATE | 3 字节 | 仅需日期的场景(如生日、下单日期) | 避免用 DATETIME 存储仅需日期的数据(节省空间) |
时间型设计规范
-
优先用
DATETIME(3):兼顾精度(毫秒)和存储开销,满足绝大多数业务场景; -
禁止用
VARCHAR存储时间:无法直接排序 / 范围查询(如WHERE create_time > '2025-01-01'); -
通用时间字段配置:
create_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', update_time DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间'
(4)特殊类型字段
| 类型 | 适用场景 | 设计规范 |
|---|---|---|
| BOOLEAN | 布尔值(是否删除、是否启用) | MySQL 中 BOOLEAN 是 TINYINT (1) 的别名,优先用TINYINT(0 = 否,1 = 是),如is_deleted TINYINT NOT NULL DEFAULT 0 |
| ENUM | 固定枚举值 | 禁用(修改枚举值需 DDL,灵活性差),改用TINYINT+ 注释说明;仅用于永不修改的枚举(如性别:1 - 男,2 - 女) |
| JSON | 非结构化数据(如扩展字段) | MySQL 5.7 + 支持,仅用于临时扩展字段(如ext_info JSON NOT NULL DEFAULT '{}');核心数据禁止用 JSON 存储(无法高效索引) |
| BLOB | 二进制数据(如图片、文件) | 禁用(数据库存储二进制效率低),改为存储文件 URL,文件存对象存储(如 OSS) |
3、通用关键字段设计规范(全表统一)
所有业务表必须包含以下通用字段,保障数据可追溯、可管理:
| 字段名 | 类型 | 默认值 | 注释 | 设计说明 |
|---|---|---|---|---|
| id | BIGINT UNSIGNED | - | 主键 ID | 自增(AUTO_INCREMENT),InnoDB 聚簇索引最优;禁止用 UUID 作为主键 |
| create_time | DATETIME(3) | CURRENT_TIMESTAMP(3) | 创建时间 | 记录数据插入时间,不可手动修改 |
| update_time | DATETIME(3) | CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) | 更新时间 | 数据修改时自动更新,无需业务层维护 |
| create_by | BIGINT UNSIGNED | 0 | 创建人 ID | 关联用户表 ID,无创建人时默认 0 |
| update_by | BIGINT UNSIGNED | 0 | 更新人 ID | 关联用户表 ID,无更新人时默认 0 |
| is_deleted | TINYINT | 0 | 逻辑删除标识 | 0 = 未删除,1 = 已删除;禁止物理删除核心数据 |
4、关联字段设计规范
- 格式:
关联表名_关联字段名(避免歧义); - 示例:
- 订单表关联用户表:
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID,关联user_base.id'; - 订单项表关联订单表:
order_id BIGINT UNSIGNED NOT NULL COMMENT '订单ID,关联order_main.id';
- 订单表关联用户表:
- 约束:核心关联字段加索引(如订单表
user_id加普通索引),保障关联查询性能; - 禁止:关联字段类型与被关联字段不一致(如用户表
id是 BIGINT,订单表user_id是 INT)。
5、特殊场景字段设计规范
(1)敏感数据字段
- 脱敏存储:手机号(
138****8000)、身份证号(110***********1234)、银行卡号(622848*******1234); - 加密存储:核心敏感数据(如支付密码、身份证明文)用 AES 加密存储,密钥独立管理;
- 禁止:明文存储密码(需哈希 + 盐值存储,如 MD5/SHA256 + 随机盐)。
AES 是一种对称分组加密算法,用同一把密钥完成加密和解密,安全性高、性能好,是现代系统的事实标准.
(2)大字段(TEXT/LONGTEXT)
- 拆分原则:大字段必须拆到子表,主表仅保留关联 ID;
- 示例:
- 主表
user_base(用户基础信息:id、mobile、name); - 子表
user_ext(用户扩展信息:user_id、intro、avatar_url);
- 主表
- 访问原则:仅在需要时查询子表,避免主表查询携带大字段。
(3)扩展字段
- 临时扩展:用
ext1/ext2/ext3(如ext1 VARCHAR(255) NOT NULL DEFAULT '' COMMENT '临时扩展字段:存储用户标签'),仅用于短期临时需求; - 长期扩展:优先新增字段 / 子表,禁止滥用扩展字段导致数据结构混乱;
- JSON 扩展:仅用于非核心、非查询的扩展数据(如
ext_info JSON NOT NULL DEFAULT '{}' COMMENT '用户扩展信息,格式:{"hobby":"读书","address":"北京"}')。
(4)枚举类字段
-
类型:优先用
TINYINT,禁止用ENUM(修改枚举值需 DDL); -
注释:必须明确枚举值含义,示例:
pay_type TINYINT NOT NULL DEFAULT 0 COMMENT '支付方式:0-未支付,1-微信支付,2-支付宝,3-银行卡支付' -
扩展:新增枚举值时,仅需更新注释,无需修改字段类型(优于 ENUM)。
6、反模式(禁止做法)
- 字段类型过大(如用
INT存储年龄、VARCHAR(255)存储姓名); - 用
VARCHAR存储数值 / 时间(如VARCHAR(11)存储手机号、VARCHAR(20)存储日期); - 核心字段允许 NULL 值(如
user_mobile VARCHAR(11) NULL); - 滥用 TEXT 类型(如用 TEXT 存储短备注);
- 枚举类字段用
VARCHAR(如status VARCHAR(10)存储 "待支付 / 已支付"); - 关联字段类型不匹配(如主表 ID 是 BIGINT,关联字段是 INT);
- 无注释的字段(如
col1 INT NOT NULL,无法理解业务含义); - 物理删除核心数据(未设置
is_deleted逻辑删除字段)。
四、索引设计规范
1、通用基础规则(所有索引必遵循)
(1)命名规范(见名知意,统一格式)
-
格式:
索引类型前缀_表名_字段名(多字段取前 3 个,下划线分隔); -
索引类型前缀(统一):
索引类型 前缀 示例 主键索引 pk_ pk_order_main_id 唯一索引 uk_ uk_user_base_mobile 普通索引 idx_ idx_order_main_user_id 全文索引 ft_idx_ ft_idx_goods_desc 空间索引 sp_idx_ sp_idx_location_geo -
规则:
- 仅用小写字母、数字、下划线,以前缀开头,禁止大写 / 特殊字符;
- 表名 / 字段名用缩写时需统一(如
order_main缩写为om,需团队共识); - 联合索引字段名按顺序拼接(如
idx_order_main_user_id_create_time)。
(2)索引类型选择原则
优先选择适配查询场景的索引类型,避免 "一刀切" 使用 B + 树索引:
| 索引类型 | 适用场景 | 禁用场景 | 注意事项 |
|---|---|---|---|
| B + 树索引(默认) | 等值查询(=)、范围查询(>/-/<)、排序(ORDER BY)、分组(GROUP BY) | 全文模糊匹配、空间坐标查询 | 遵循 "最左前缀",InnoDB 聚簇索引基于此实现 |
| 唯一索引 | 业务唯一字段(手机号、订单编号、用户账号) | 非唯一字段(如状态、创建时间) | 避免过度使用(写入时需校验唯一性,增加开销) |
| 全文索引(FULLTEXT) | 长文本模糊匹配(商品描述、文章内容) | 短字符串、等值 / 范围查询 | 仅 InnoDB(5.6+)/MyISAM 支持,仅适用于 CHAR/VARCHAR/TEXT |
| 空间索引(SPATIAL) | 地理坐标查询(经纬度、地理位置) | 非空间数据查询 | 仅 InnoDB(5.7+)/MyISAM 支持,需用空间类型字段(GEOMETRY/POINT) |
| 哈希索引 | 仅等值查询(Memory 引擎场景) | 范围查询、排序 | MySQL 仅 Memory 引擎原生支持,InnoDB 自适应哈希索引为隐式优化(无需手动创建) |
(3)索引数量控制
- 单表索引总数≤5 个:索引越多,INSERT/UPDATE/DELETE 的写入开销越大;
- 联合索引替代多个单列索引:如
(user_id, create_time)可覆盖user_id和user_id+create_time查询,避免重复创建idx_user_id。
2、核心设计原则(B + 树索引为主)
(1)最左前缀匹配原则(核心中的核心)
- 联合索引
(a,b,c)仅匹配以下查询场景(按优先级):WHERE a=1(仅左前缀 a);WHERE a=1 AND b=2(左前缀 a+b);WHERE a=1 AND b=2 AND c=3(全字段);
- 失效场景:
WHERE b=2、WHERE b=2 AND c=3、WHERE a=1 AND c=3(跳过 b); - 设计技巧:联合索引字段按 "查询频率高→低、区分度高→低" 排序(如
(user_id, create_time),user_id 查询更频繁、区分度更高)。
(2)高区分度优先原则
- 区分度公式:
区分度 = 不同值数量 / 总行数(值越接近 1,区分度越高); - 示例:
- 手机号(区分度≈1)> 用户 ID(区分度≈0.8)> 订单状态(区分度≈0.01);
- 联合索引中,区分度高的字段放左侧(如
(mobile, status)而非(status, mobile));
- 禁忌:为低区分度字段创建单列索引(如
status(仅 1/2/3 三个值),优化器会选择全表扫描而非索引)。
(3)覆盖索引优先原则
- 定义:查询的所有字段(SELECT/WHERE/ORDER BY)均包含在索引中,无需回表(Extra 显示
Using index); - 设计示例:
- 业务查询:
SELECT id, name FROM user WHERE age=20; - 索引设计:
idx_age_name (age, name)(id 是主键,InnoDB 聚簇索引自动包含,无需显式加入);
- 业务查询:
- 优势:避免回表操作,将随机 IO 转为顺序 IO,查询效率提升 5~10 倍。
(4)索引列禁用操作原则
索引列做以下操作会导致索引失效,需优先规避:
| 禁用操作 | 示例(❌) | 优化方案(✅) |
|---|---|---|
| 函数 / 运算 | WHERE DATE(create_time)='2025-01-01' |
WHERE create_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-01 23:59:59' |
| 隐式类型转换 | WHERE mobile=13800138000(mobile 为字符串) |
WHERE mobile='13800138000' |
| 负向查询 | WHERE status != 1/WHERE id NOT IN (1,2,3) |
改写为正向查询(如WHERE status IN (2,3)),或业务层过滤 |
| 模糊匹配(% 开头) | WHERE name LIKE '%张三' |
改用全文索引(FULLTEXT),或业务前置处理(如关键词分词) |
| OR 连接无索引字段 | WHERE a=1 OR b=2(仅 a 有索引) |
拆分查询(SELECT * FROM tbl WHERE a=1 UNION ALL SELECT * FROM tbl WHERE b=2),或给 b 加索引 |
(5)短索引原则(字符串字段)
- 字符串字段(如手机号、邮箱、URL)创建前缀索引 ,减少索引存储空间:
- 格式:
INDEX idx_mobile (mobile(11))(手机号固定 11 位); - 计算最优前缀长度:通过
SELECT COUNT(DISTINCT LEFT(col, n))/COUNT(*) FROM tbl,取区分度≥95% 的最小 n;
- 格式:
- 示例:邮箱字段
email,计算得LEFT(email, 10)区分度 98%,则创建idx_email (email(10))。
3、分场景索引设计规范
(1)主键索引设计
- 必选规则:所有 InnoDB 表必须显式创建主键索引,禁止依赖隐式主键;
- 类型选择:
- 优先自增主键:
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY(InnoDB 聚簇索引,避免页分裂); - 复合主键:仅用于多对多关联表(如
user_role表,主键(user_id, role_id));
- 优先自增主键:
- 禁忌:禁止用 UUID/MD5 值作为主键(离散写入导致索引碎片、IO 飙升)。
(2)唯一索引设计
- 适用场景:业务唯一标识字段(手机号、订单编号、用户账号);
- 设计技巧:
- 允许空值的唯一字段:若业务允许
NULL,需注意UNIQUE KEY允许多个NULL(可通过uk_mobile (mobile)+mobile NOT NULL规避); - 组合唯一:多字段组合唯一(如
uk_user_date (user_id, create_date),限制用户单日仅 1 条记录);
- 允许空值的唯一字段:若业务允许
- 禁忌:禁止为非唯一字段创建唯一索引(如
uk_status (status),会导致写入失败)。
(3)联合索引设计
- 字段顺序优先级:
查询频率高 > 区分度高 > 等值查询字段 > 范围查询字段; - 示例:
- 业务查询:
SELECT * FROM order_main WHERE user_id=123 AND create_time > '2025-01-01' ORDER BY amount; - 索引设计:
idx_user_create_amount (user_id, create_time, amount)(user_id 等值查询→create_time 范围查询→amount 排序);
- 业务查询:
- 禁忌:范围查询字段后加等值字段(如
(create_time, user_id),user_id 无法命中索引)。
(4)排序 / 分组索引设计
- 规则:
ORDER BY/GROUP BY字段必须包含在索引中,且顺序与查询一致; - 示例:
- 业务查询:
SELECT user_id, COUNT(*) FROM order_main WHERE status=1 GROUP BY user_id ORDER BY COUNT(*) DESC; - 索引设计:
idx_status_user (status, user_id)(覆盖 WHERE+GROUP BY,排序可由优化器优化);
- 业务查询:
- 禁忌:排序字段与索引顺序相反(如索引
(a,b),查询ORDER BY b,a DESC,导致Using filesort)。
(5)大表 / 分区表索引设计
-
单表数据量 > 1000 万:优先创建分区表 + 分区内索引,避免全表索引扫描;
-
分区表索引:
- 本地索引(LOCAL):索引与分区一一对应,查询仅扫描目标分区(推荐);
- 全局索引(GLOBAL):仅用于跨分区查询的主键 / 唯一索引;
-
示例:
CREATE TABLE order_main ( id BIGINT UNSIGNED AUTO_INCREMENT, user_id BIGINT UNSIGNED, create_time DATETIME(3), PRIMARY KEY pk_order_main_id (id, create_time) -- 分区键必须包含在主键中 ) ENGINE=InnoDB PARTITION BY RANGE (TO_DAYS(create_time)) ( PARTITION p202501 VALUES LESS THAN (TO_DAYS('2025-02-01')), PARTITION p202502 VALUES LESS THAN (TO_DAYS('2025-03-01')) ); CREATE INDEX idx_user_id ON order_main (user_id) LOCAL; -- 本地索引
4、索引维护规范
(1)索引有效性校验
- 定期检查未使用索引:通过
sys.schema_unused_indexes(MySQL 5.7+)查询,删除冗余 / 无用索引; - 检查索引碎片:
- 查看碎片率:
SELECT TABLE_NAME, DATA_FREE/(DATA_LENGTH+INDEX_LENGTH) AS frag_rate FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='db_name'; - 碎片率 > 30% 时重建索引:
ALTER TABLE tbl_name ENGINE=InnoDB(InnoDB 无锁重建),或OPTIMIZE TABLE tbl_name(有锁,低峰期执行);
- 查看碎片率:
- 验证执行计划:用
EXPLAIN ANALYZE(MySQL 8.0+)检查索引是否命中,重点关注type(避免 ALL)、key(非 NULL)、Extra(无 Using temporary/Using filesort)。
(2)索引变更规范
- 大表索引操作:
- MySQL 8.0+:用
ALTER TABLE tbl ADD INDEX idx_col (col) ALGORITHM=INPLACE, LOCK=NONE(无锁在线 DDL); - MySQL 5.7:选择业务低峰期执行,避免锁表导致读写阻塞;
- MySQL 8.0+:用
- 变更流程:测试环境验证→预发环境灰度→生产环境执行,变更后监控 QPS / 响应时间 / 锁等待;
- 禁忌:禁止在大事务期间创建 / 删除索引(加剧锁竞争)。
(3)索引冗余识别与清理
以下场景判定为冗余索引,需及时删除:
- 已有联合索引
(a,b),又创建单列索引(a); - 索引
(a,b)与(a,b,c),且(a,b)无单独查询场景; - 索引字段与主键重复(如主键
id,又创建idx_id (id)); - 索引创建后长期未使用(超过 30 天无命中)。
5、索引SET规范
索引中,尽量避免使用外键约束。不建议使用外键约束,但是一定要在表与表之间的关联上建立索引。因为外键可用于保证数据的参照完整性,但建议在业务端实现就可以了。且外键会影响父表和子表的写操作从而降低性能。
6、反模式(禁止做法)
- 为所有字段创建索引(单表索引 > 5 个,写入性能下降 50% 以上);
- 联合索引违反最左前缀原则(如查询
b=2,索引(a,b)); - 索引列使用函数 / 运算(如
WHERE id+1=100); - 小表(<10 万行)创建大量索引(优化器优先全表扫描,索引无意义);
- 用唯一索引约束非唯一字段(如
uk_status (status)); - 主键用 UUID / 字符串(离散写入导致索引碎片);
- 忽略覆盖索引,频繁回表查询(如
SELECT *导致Using index condition); - 在线上核心表随意删除索引(导致慢查询爆发)。
五、SQL开发的规范
1、运算逻辑:数据库做 "存储",业务层做 "计算"
(1)核心规范
- 禁止数据库内执行复杂运算 :所有复杂计算(如多维度统计、数据格式化、复杂公式运算、字符串拼接 / 拆分、日期差值高阶计算等)迁移至业务应用层完成;
- 反例:
SELECT id, (price * num + IF(discount>0, discount, 0)) * 1.13 AS final_amount FROM order_item;(数据库计算最终金额); - 正例:仅查询基础字段
id, price, num, discount,由业务代码计算最终金额;
- 反例:
- 简化数据库端轻量运算 :仅保留必要的基础运算(如简单加减、NULL 值判断
IFNULL()),且优先通过索引 / 字段设计规避(如新增total_price字段存储price*num结果,避免实时计算)。
(2)性能影响
数据库的核心优势是数据存储和快速检索,而非复杂逻辑运算;复杂运算会占用数据库 CPU / 内存资源,导致并发处理能力下降,迁移至业务层可分散计算压力,提升整体系统吞吐量。
2、索引利用:最大化已有索引价值
核心规范
- 所有查询必须命中索引 :编写 SQL 前先确认表上已有索引,WHERE/ORDER BY/GROUP BY 字段必须匹配索引(遵循最左前缀原则);
- 反例:表有索引
idx_user_create_time (user_id, create_time),却执行SELECT * FROM order WHERE create_time > '2025-01-01'(违反最左前缀,索引失效); - 正例:补充
user_id条件或调整索引,执行SELECT id FROM order WHERE user_id=123 AND create_time > '2025-01-01'(命中索引);
- 反例:表有索引
- 禁止索引列函数 / 计算 :WHERE 从句中严禁对列做函数转换、运算,避免索引失效;
- 反例:
WHERE DATE(create_time)='2025-01-01'、WHERE id+1=100、WHERE SUBSTR(mobile,1,3)='138'; - 正例:
WHERE create_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-01 23:59:59'、WHERE id=99、WHERE mobile LIKE '138%';
- 反例:
- 避免隐式类型转换 :确保查询值与字段类型一致,防止索引失效;
- 反例:字符串字段
mobile用数值匹配WHERE mobile=13800138000; - 正例:
WHERE mobile='13800138000'。
- 反例:字符串字段
3、查询规范:精准、高效、可控
核心规范
- 禁用 SELECT *,强制指定字段列表: 仅查询业务所需字段,减少网络传输、回表 IO 开销,同时避免因表结构变更导致的 SQL 异常;
- 反例:
SELECT * FROM user_base WHERE id=123;; - 正例:
SELECT id, user_name, mobile FROM user_base WHERE id=123;;
- 反例:
- 禁用 ORDER BY RAND () 随机排序 :该操作会全表扫描并生成随机值,性能极差;
- 反例:
SELECT id FROM goods ORDER BY RAND() LIMIT 10;; - 正例:业务层先获取主键范围,随机生成 ID 后查询(如
SELECT id FROM goods WHERE id IN (101,205,308));
- 反例:
- OR 判断替换为 IN :同一列的多值 OR 判断,用 IN 替代(优化器更易命中索引);
- 反例:
WHERE status=1 OR status=2 OR status=3; - 正例:
WHERE status IN (1,2,3);
- 反例:
- 拆分复杂大 SQL 为多个小 SQL :单个复杂 SQL(多表关联、多层聚合、大结果集)会占用数据库连接和 CPU 资源,拆分后降低单次执行开销;
- 反例:一次性关联 8 张表查询 + 聚合 + 排序;
- 正例:先查询核心表数据,再按需查询关联表,业务层拼接结果;
- 限制 JOIN 关联表数量 :单次 JOIN 表数≤5 张,过多表关联会导致优化器选择低效执行计划,且锁竞争风险升高;
- 说明:超过 5 张表的关联需求,优先拆分查询或通过中间表预处理数据。
4、写入规范:安全、明确、低风险
核心规范
- 禁用不含字段列表的 INSERT 语句 :必须显式指定插入字段,避免表结构变更(如新增字段)导致的插入失败或数据错位;
- 反例:
INSERT INTO user_base VALUES (NULL, '13800138000', '张三');; - 正例:
INSERT INTO user_base (mobile, user_name) VALUES ('13800138000', '张三');;
- 反例:
- 优先使用预编译语句 :业务代码中通过 PreparedStatement/ORM 参数绑定执行 SQL,禁止字符串拼接;
- 优势:防止 SQL 注入、复用执行计划、避免重复解析 SQL,提升执行效率;
- 示例(MyBatis :
SELECT id FROM user WHERE mobile = #{mobile}(参数绑定),而非${mobile}(字符串拼接)。
5、语法优化:规避低效写法
核心规范
- 子查询替换为 JOIN 操作 :子查询易生成临时表,JOIN 更易利用索引,执行效率更高;
- 反例:
SELECT * FROM user WHERE id IN (SELECT user_id FROM order WHERE status=1);; - 正例:
SELECT u.* FROM user u JOIN order o ON u.id=o.user_id WHERE o.status=1;;
- 反例:
- UNION ALL 替代 UNION(无重复场景) :UNION 会触发去重排序(额外开销),确认无重复值时必须用 UNION ALL;
- 反例:
SELECT id FROM order_2024 UNION SELECT id FROM order_2025;(无重复却用 UNION); - 正例:
SELECT id FROM order_2024 UNION ALL SELECT id FROM order_2025;。
- 反例:
6、交互规范:减少数据库访问频次
核心规范
- 降低同数据库交互次数 :
- 批量操作替代循环单条:如批量 INSERT(
VALUES (1,2),(3,4))、批量 UPDATE(按 ID 区间分批); - 缓存热点数据:将高频查询的静态 / 准静态数据(如商品基础信息、字典表)缓存至 Redis,避免重复查询数据库;
- 合并多次查询:业务层整合查询需求,单次 SQL 获取多维度数据(而非多次单字段查询);
- 批量操作替代循环单条:如批量 INSERT(
- 不同数据库使用独立连接:程序访问不同数据库(如订单库、用户库)时,使用独立的数据库连接池,避免连接混用导致的事务异常、性能干扰。
六、操作行为规范
1、超大规模批量写操作规范
针对单批次操作超 100 万行的批量写场景,核心原则是分批执行、降低单次操作风险:
(1)强制分批操作:
-
拆分阈值:将超 100 万行的批量写操作拆分为若干批次,单批次操作行数建议控制在 1000~5000 行(可根据数据库负载调整),避免单次操作占用大量锁资源、引发事务超时或阻塞;
-
分批方式:基于主键 / 唯一索引的范围拆分(如按 ID 区间
WHERE id BETWEEN 1 AND 1000),避免全表扫描加锁; -
示例(UPDATE 分批):
-- 分批更新100万行订单状态,每次1000行
UPDATE order_main SET status=3 WHERE id BETWEEN 1 AND 1000 AND is_deleted=0;
UPDATE order_main SET status=3 WHERE id BETWEEN 1001 AND 2000 AND is_deleted=0;
-- 依次执行直至完成,每批次执行后可短暂休眠(如100ms),降低数据库压力
(2)操作时机与监控:
- 执行时机:批量写操作需在业务低峰期(如凌晨)执行,避免影响核心业务;
- 实时监控:执行过程中监控数据库 CPU、IO、锁等待指标,出现异常立即暂停操作;
(3)INSERT 批量优化:
- 超 100 万行 INSERT 同样分批,单批次 INSERT VALUES 数量≤5000 行,避免生成超大事务日志,同时禁用
INSERT ... SELECT直接批量插入(改为 "SELECT 导出→分批 INSERT")。
2、大表结构变更规范
针对数据量大的表(通常指千万级以上或占用空间超 10GB),核心原则是无锁 / 低锁变更、避免业务中断:
- 强制使用 pt-online-schema-change 工具 :
- 禁用原生 ALTER TABLE 直接修改大表结构(会锁表,导致读写阻塞),必须使用 Percona Toolkit 的
pt-online-schema-change工具; - 工具优势:通过创建临时表、增量同步数据、交换表名的方式实现无锁表结构变更,全程不阻塞业务读写;
- 操作要点:
- 变更前备份表结构和数据,确认工具参数(如
--alter指定修改内容、--no-drop-old-table保留旧表); - 低峰期执行,执行过程中监控同步延迟和数据库负载,避免增量同步占用过多资源;
- 变更前备份表结构和数据,确认工具参数(如
- 禁用原生 ALTER TABLE 直接修改大表结构(会锁表,导致读写阻塞),必须使用 Percona Toolkit 的
- 变更前验证 :
- 大表结构变更(加索引、改字段类型、新增字段等)需先在测试环境验证,确认变更耗时、资源占用及对业务的影响;
- 优先选择 "非侵入式" 变更(如新增字段设默认值、加索引用 INPLACE 算法),避免重写全表数据。
3、数据库账号权限管控规范
针对程序连接数据库的账号,核心原则是最小权限、禁止高危权限:
(1)禁用 SUPER 权限:
程序使用的数据库账号严禁赋予SUPER权限(该权限包含修改全局参数、终止连接、管理复制等高危操作),防止程序异常 / 漏洞导致数据库配置被篡改;
(2)遵循最小权限原则:
按业务场景分配精准权限,禁止赋予全库 / 全表的万能权限:
| 业务场景 | 允许权限 | 禁止权限 |
|---|---|---|
| 只读查询 | SELECT | INSERT/UPDATE/DELETE/ALTER |
| 读写业务 | SELECT/INSERT/UPDATE/DELETE | ALTER/DROP/CREATE/GRANT |
| 数据导入(运维) | INSERT/SELECT | SUPER/ALL PRIVILEGES |
示例(创建只读账号):
CREATE USER 'app_read'@'%' IDENTIFIED BY 'xxx';
GRANT SELECT ON mall_order.* TO 'app_read'@'%'; -- 仅授予指定库的只读权限
FLUSH PRIVILEGES;
(3)账号隔离规范:
- 不同环境(测试 / 预发 / 生产)、不同业务模块(订单 / 用户 / 支付)使用独立数据库账号,避免账号混用导致权限泄露或故障扩散;
- 禁止程序使用 root / 管理员账号连接数据库,所有程序账号需设置强密码并定期更换。