MYSQL 团队脚本规范
一、核心原则
-
性能优先:编写的SQL需避免全表扫描、无效索引使用等性能问题,保障数据库高并发、高可用运行。
-
可读性优先:命名、格式统一,便于开发团队协作维护和后续问题排查。
-
安全性优先:防止SQL注入、敏感数据泄露等安全风险。
-
规范性统一:所有开发团队成员严格遵循本规范,减少个性化差异导致的维护成本。
二、命名规范
2.1 通用规则
-
统一使用小写字母 + 下划线分隔,禁止使用大写字母、中文、特殊字符(除下划线外)。示例:正确(sys_user_t、phone_num),错误(SysUserT、用户表、user#name);
-
命名需见名知意,体现业务含义,避免拼音缩写(行业通用缩写除外,如user、order)。示例:正确(order_amount、product_id),错误(yhsj_je(用户数据金额)、sp_id(商品ID));
-
禁止使用MySQL保留字(如select、table、where等),若确需使用,必须用反引号(
)包裹。示例:正确(order_t、select`_flag),错误(order_t、select_flag); -
命名长度控制在30个字符以内,避免过长影响可读性。示例:正确(biz_order_payment_record_t),错误(biz_order_payment_transaction_record_detail_t(长度超30));
2.2 各对象命名规则
| 对象类型 | 命名格式 | 示例 | 备注 |
|----------|----------|------|------|
| 数据库 | 业务模块名 + _db | user_center_db、order_trade_db | 若为分库,可在后缀加序号,如order_trade_db_01 |
| 数据表 | 业务表名 + t | sys_user_t、biz_order_t | 前缀区分系统表(sys)和业务表(biz_),分表加序号后缀,如biz_order_t_001 |
| 字段 | 业务含义 + 字段类型(可选) | user_name、order_id、phone_num | 禁止使用id作为非主键字段名,主键统一规范见下文 |
| 主键 | 表名(缩写) + _id | user_id、order_id | 统一为自增或雪花算法主键,避免复合主键 |
| 索引 | 表名 + _ + 字段名(多字段用下划线连接) + 索引类型(_idx普通索引/_uk唯一索引/_pk主键索引) | sys_user_user_name_idx、biz_order_order_no_uk | 联合索引字段按查询优先级排序命名 |
| 视图 | 业务含义 + _v | biz_order_user_v、sys_role_permission_v | 避免复杂视图,视图命名体现关联表或业务场景 |
| 存储过程 | 业务功能 + _sp | biz_order_calc_amount_sp | 尽量少用存储过程,确需使用时严格遵循命名规范 |
| 函数 | 业务功能 + _func | sys_user_encrypt_pwd_func | 避免自定义函数影响SQL性能,优先在应用层实现 |
| 触发器 | 表名 + _ + 触发时机(before/after) + 操作(insert/update/delete) + _trg | biz_order_after_insert_trg | 尽量少用触发器,避免逻辑隐蔽性过高 |
三、数据库对象设计规范
3.1 数据库设计
-
单个数据库实例下,数据库数量控制在20个以内,避免资源竞争。
-
按业务模块垂直分库,避免一个数据库包含多个不相关业务表。
-
字符集统一使用utf8mb4,兼容所有Unicode字符(包括Emoji表情),避免乱码问题;排序规则统一使用utf8mb4_general_ci(性能优先,若需精准排序可使用utf8mb4_unicode_ci)。
3.2 数据表设计
-
存储引擎优先使用InnoDB,支持事务、行级锁、外键约束和崩溃恢复,避免使用MyISAM(无事务、表级锁,性能差)。
-
表名规范:统一遵循"业务前缀_表含义_t"格式,系统表前缀固定为"sys_",业务表前缀固定为"biz_";分表场景需在表名末尾添加下划线+序号后缀(如biz_order_t_001);命名仅允许小写字母和下划线,禁止使用大写字母、中文、特殊字符及MySQL保留字,确需使用保留字时必须用反引号(`)包裹,且命名需见名知意,体现核心业务含义,避免拼音缩写(行业通用缩写除外)。
-
表名长度限制:表名总长度需控制在30个字符以内(含下划线),避免因表名过长导致可读性下降,同时兼容部分数据库工具对表名长度的限制,减少后续运维风险。
-
每张表必须包含主键,优先使用自增整数主键(BIGINT UNSIGNED类型),若需分布式唯一ID,使用雪花算法(同样为BIGINT类型),禁止使用UUID作为主键(会导致索引碎片严重,性能下降)。
-
字段设计最小化原则:选择满足业务需求的最小数据类型,减少存储空间占用和IO开销,示例如下:
-
整数类型:优先使用TINYINT(1字节)、SMALLINT(2字节)、INT(4字节)、BIGINT(8字节),避免无脑使用BIGINT;状态字段优先用TINYINT(如status TINYINT COMMENT '0-禁用,1-启用')。
-
字符串类型:固定长度字符串用CHAR,可变长度用VARCHAR,避免使用TEXT/BLOB大字段;若存储长度超过5000字符,考虑拆分表或存储到文件系统,仅在数据库中保存文件路径。
-
时间类型:优先使用DATETIME(存储范围广,不受时区影响,格式YYYY-MM-DD HH:MM:SS),若需自动记录创建/更新时间,可使用TIMESTAMP(4字节,受时区影响);禁止使用VARCHAR存储时间(无法进行时间函数运算,易出现格式混乱)。
-
字段尽量避免允许NULL值:默认设置为非NULL,并用合理默认值替代(如字符串默认空串'',数值类型默认0,时间类型默认CURRENT_TIMESTAMP),NULL值会增加索引维护成本和查询复杂度。
-
每张表字段数量控制在50个以内:若字段过多,说明表设计不合理,需进行垂直拆分(将大字段、不常用字段拆分到子表)。
-
注释强制要求:表注释和字段注释必须完整且不能为空,其中字段注释为强制要求,无例外。注释需清晰说明字段的业务含义,若为枚举类型需明确枚举值对应的含义,便于后续维护和开发团队协作,格式如下:
sql
-- 表注释
CREATE TABLE sys_user_t (
user_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户主键ID',
user_name VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户姓名',
phone_num VARCHAR(11) NOT NULL DEFAULT '' COMMENT '手机号码',
status TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '用户状态:0-禁用,1-启用',
created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
created_by varchar(100) DEFAULT NULL COMMENT '创建人',
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
updated_by varchar(100) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (user_id) COMMENT '主键索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '系统用户表';
- 统一添加审计字段:所有业务表必须包含以下四个审计字段,确保数据变更可追溯:
created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
created_by varchar(100) DEFAULT NULL COMMENT '创建人',
updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
updated_by varchar(100) DEFAULT NULL COMMENT '更新人';
四、SQL语法编写规范
4.1 通用语法规则
- SQL语句必须格式化:关键字(SELECT、FROM、WHERE、JOIN等)单独换行,缩进统一使用4个空格(禁止使用Tab),提升可读性,示例:
sql
-- 规范写法
SELECT
u.user_id,
u.user_name,
o.order_id,
o.order_amount
FROM
sys_user_t u
LEFT JOIN
biz_order_t o
ON
u.user_id = o.user_id
WHERE
u.status = 1
AND o.create_time >= '2025-01-01 00:00:00'
ORDER BY
o.create_time DESC
LIMIT 100;
-- 不规范写法(紧凑格式,可读性差)
select u.user_id,u.user_name,o.order_id,o.order_amount from sys_user_t u left join biz_order_t o on u.user_id=o.user_id where u.status=1 and o.create_time>='2025-01-01 00:00:00' order by o.create_time desc limit 100;
-
关键字统一使用大写(便于区分关键字和业务字段/表名,提升可读性),字段名、表名使用小写。
-
禁止使用SELECT *:必须明确指定需要查询的字段,减少不必要的IO开销(避免读取无用字段),同时避免因表结构变更导致的SQL异常。
-
SQL语句末尾必须加分号,作为语句结束标识。
-
复杂SQL需添加注释:说明业务逻辑、查询目的、特殊处理原因等,注释使用-- (单行注释)或/* */(多行注释)。
4.2 查询语句规范
4.2.1 WHERE条件优化
-
优先使用"等值查询"(=),再使用范围查询(>、<、BETWEEN等),最后使用模糊查询(LIKE)。
-
禁止在索引字段上进行函数运算或类型转换,会导致索引失效,触发全表扫描,示例:
sql
-- 不规范(索引字段user_id进行了函数运算,索引失效)
SELECT * FROM sys_user_t WHERE SUBSTR(user_id, 1, 2) = '10';
-- 规范(避免索引字段运算)
SELECT * FROM sys_user_t WHERE user_id >= 1000 AND user_id < 1100;
-- 不规范(索引字段phone_num进行了类型转换,索引失效)
SELECT * FROM sys_user_t WHERE phone_num = 13800138000;
-- 规范(保持字段类型一致)
SELECT * FROM sys_user_t WHERE phone_num = '13800138000';
- LIKE模糊查询禁止使用%xxx(前缀模糊匹配),可使用xxx%(后缀模糊匹配,索引有效),示例:
sql
-- 不规范(前缀%,索引失效,全表扫描)
SELECT * FROM sys_user_t WHERE user_name LIKE '%zhang';
-- 规范(后缀%,索引有效)
SELECT * FROM sys_user_t WHERE user_name LIKE 'zhang%';
- 多条件查询时,将过滤性强的条件放在前面,提升查询效率。
4.2.2 JOIN关联查询
-
关联表数量控制在5张以内,避免过多关联导致查询性能急剧下降。
-
遵循"小表驱动大表"原则:INNER JOIN(禁止缩写为JOIN)时数据库会自动优化,LEFT JOIN时左表为小表,RIGHT JOIN时右表为小表,减少循环次数。
-
关联字段必须建立索引,且字段类型、字符集一致,避免隐式转换导致索引失效。
-
禁止使用CROSS JOIN(笛卡尔积),会产生大量无效数据,严重占用数据库资源。
4.2.3 ORDER BY与GROUP BY优化
-
ORDER BY字段尽量使用索引字段,避免出现Using filesort(文件排序,性能差),若需多字段排序,需遵循联合索引的最左前缀原则。
-
GROUP BY字段尽量使用索引字段,避免出现Using temporary(临时表,性能差),且GROUP BY后必须使用ORDER BY NULL(若无需排序),关闭默认排序,示例:
sql
-- 规范(无需排序时,关闭GROUP BY默认排序)
SELECT user_id, COUNT(*) AS order_count
FROM biz_order_t
GROUP BY user_id
ORDER BY NULL;
4.2.4 分页查询优化
- 大分页(OFFSET值过大)时,禁止使用LIMIT offset, size(会扫描前offset条数据并丢弃,性能差),优先使用"主键ID过滤",示例:
sql
-- 不规范(offset=100000,性能差)
SELECT * FROM biz_order_t ORDER BY order_id DESC LIMIT 100000, 10;
-- 规范(主键过滤,性能优异)
SELECT * FROM biz_order_t WHERE order_id < (SELECT order_id FROM biz_order_t ORDER BY order_id DESC LIMIT 100000, 1) ORDER BY order_id DESC LIMIT 10;
- 分页查询必须指定LIMIT,避免返回大量数据导致内存溢出。
4.3 插入语句规范
- 优先使用批量插入(INSERT INTO ... VALUES (...), (...), (...)),替代单条循环插入,减少数据库连接次数和日志写入开销,示例:
sql
-- 规范(批量插入,每批次控制在1000条以内)
INSERT INTO sys_user_t (user_name, phone_num, status)
VALUES
('zhang san', '13800138001', 1),
('li si', '13800138002', 1),
('wang wu', '13800138003', 0);
- 插入语句必须指定字段名,禁止省略字段名直接插入值,避免因表结构变更(如字段顺序调整、新增字段)导致插入失败,示例:
sql
-- 不规范(省略字段名,表结构变更易出错)
INSERT INTO sys_user_t VALUES (null, 'zhang san', '13800138001', 1, NOW(), NOW());
-- 规范(指定字段名,兼容性强)
INSERT INTO sys_user_t (user_name, phone_num, status, create_time, update_time)
VALUES ('zhang san', '13800138001', 1, NOW(), NOW());
- 批量插入每批次数据量控制在1000条以内,避免单次插入数据量过大导致事务超时或锁等待。
4.4 更新/删除语句规范
-
必须添加WHERE条件:除非确需更新/删除全表数据,否则禁止省略WHERE条件,避免误操作导致全表数据丢失/篡改;若确需操作全表,必须添加注释说明,并经过团队评审。
-
避免批量更新/删除大表数据:若需操作大量数据(超过1000条),需分批次执行(每次1000条以内),并添加事务控制,避免长时间占用锁导致其他业务阻塞,示例:
sql
-- 分批次更新示例
WHILE 1 DO
UPDATE biz_order_t
SET order_status = 2
WHERE order_id <= 10000 AND order_status = 1
LIMIT 1000;
-- 无数据更新时退出循环
IF ROW_COUNT() = 0 THEN
LEAVE;
END IF;
-- 可选:添加延时,减少数据库压力
DO SLEEP(1);
END WHILE;
-
更新语句尽量只更新需要变更的字段,禁止更新未变更的字段,减少日志写入和索引维护开销。
-
禁止在事务中执行大量更新/删除操作,避免长事务导致锁等待和事务日志暴涨。
4.5 事务使用规范
-
遵循"小事务原则":事务中仅包含必要的SQL语句,避免无关操作进入事务,减少事务持有锁的时间。
-
明确事务隔离级别:MySQL默认隔离级别为REPEATABLE READ(可重复读),满足大部分业务场景;若需更高一致性,使用SERIALIZABLE(串行化,性能差);若需更高性能,可使用READ COMMITTED(读已提交),禁止使用READ UNCOMMITTED(读未提交,会出现脏读)。
-
事务中避免包含用户交互操作(如等待用户输入),防止事务长时间未提交,导致锁资源被占用。
-
必须使用TRY...CATCH(应用层)或BEGIN...COMMIT/ROLLBACK(数据库层)确保事务原子性,异常时及时回滚,示例:
sql
-- 数据库层事务示例
BEGIN;
TRY
INSERT INTO sys_user_t (user_name, phone_num) VALUES ('zhang san', '13800138001');
INSERT INTO biz_order_t (user_id, order_amount) VALUES (LAST_INSERT_ID(), 100.00);
COMMIT;
CATCH
ROLLBACK;
-- 记录异常日志
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '用户创建及订单生成失败';
END TRY;
五、索引设计规范
5.1 索引创建原则
-
按需创建:仅为查询频繁、过滤性强的字段创建索引,避免过度索引(插入/更新/删除操作会维护索引,过度索引会导致写入性能下降)。
-
联合索引优先:若查询需使用多个字段作为条件,优先创建联合索引,替代多个单字段索引(联合索引查询效率更高,占用存储空间更少)。
-
联合索引最左前缀原则:创建联合索引时,按字段查询频率从高到低、过滤性从强到弱排序,查询时需满足最左前缀匹配,索引才会生效,示例:
sql
-- 创建联合索引:user_name -> phone_num -> status
CREATE INDEX sys_user_user_name_phone_num_status_idx ON sys_user_t (user_name, phone_num, status);
-- 有效匹配(满足最左前缀:user_name)
SELECT * FROM sys_user_t WHERE user_name = 'zhang san';
-- 有效匹配(满足最左前缀:user_name -> phone_num)
SELECT * FROM sys_user_t WHERE user_name = 'zhang san' AND phone_num = '13800138001';
-- 有效匹配(满足最左前缀:user_name -> phone_num -> status)
SELECT * FROM sys_user_t WHERE user_name = 'zhang san' AND phone_num = '13800138001' AND status = 1;
-- 无效匹配(跳过user_name,索引失效)
SELECT * FROM sys_user_t WHERE phone_num = '13800138001' AND status = 1;
- 低基数字段不创建索引:基数(字段唯一值数量/总记录数)过低的字段(如gender(男/女)、is_delete(0/1)),创建索引后查询优化器会选择全表扫描,索引无效,无需创建。
5.2 索引类型选择
-
主键索引:每张表必须创建,优先自增整数类型,唯一标识表记录。
-
唯一索引:用于保证字段值唯一(如phone_num、order_no),避免重复数据,查询效率高于普通索引。
-
普通索引:用于普通查询条件,无唯一性约束,是最常用的索引类型。
-
禁止使用全文索引:MySQL全文索引功能有限,若需全文检索,优先使用Elasticsearch等专门的检索引擎。
5.3 索引维护规范
-
定期优化索引:通过EXPLAIN分析SQL执行计划,排查无效索引、冗余索引;通过OPTIMIZE TABLE优化索引碎片(仅对InnoDB引擎有效,需在业务低峰期执行)。
-
大表创建索引需谨慎:大表(记录数超过100万)创建索引会锁表(InnoDB在MySQL 5.6及以上支持在线DDL,可减少锁表时间),需在业务低峰期执行,并提前备份数据。
-
禁止删除主键索引:若确需修改主键,需先创建新主键索引,再删除旧主键索引。
六、安全规范
- 防止SQL注入:
-
应用层优先使用预处理语句(PreparedStatement)或ORM框架(如MyBatis、Hibernate),避免直接拼接SQL字符串。
-
禁止将用户输入的参数直接嵌入SQL语句,必须进行参数绑定或过滤转义。
-
示例:
java
// 不安全(直接拼接用户输入,存在SQL注入风险)
String sql = "SELECT * FROM sys_user_t WHERE user_name = '" + userName + "' AND password = '" + password + "'";
// 安全(预处理语句,参数绑定)
String sql = "SELECT * FROM sys_user_t WHERE user_name = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, userName);
pstmt.setString(2, password);
- 敏感数据保护:
-
密码等敏感数据必须加密存储:优先使用BCrypt、Argon2等不可逆加密算法,禁止明文存储或使用MD5(可被彩虹表破解)。
-
手机号、身份证号等个人信息需脱敏存储:如手机号存储为138****8000,身份证号存储为110101********1234。
- 权限控制:
-
应用连接数据库使用最小权限账号:仅授予SELECT、INSERT、UPDATE、DELETE等必要权限,禁止使用ROOT账号连接应用。
-
按业务模块分配数据库账号,不同模块账号仅能访问对应业务数据库/表,避免越权访问。
- SQL审计:
- 开启MySQL审计日志(或使用第三方审计工具),记录所有数据库操作,便于追溯安全事件。
七、可维护性规范
-
SQL脚本版本控制:所有DDL(建库、建表、改表)、DML(批量插入/更新/删除)脚本需纳入版本控制系统(如Git),记录修改人、修改时间、修改原因。
-
脚本执行规范:
- DDL脚本必须添加"如果存在则删除"的判断,避免执行失败,示例:
sql
-- 建表前先判断表是否存在
DROP TABLE IF EXISTS sys_user_t;
CREATE TABLE sys_user_t (...);
-- 创建索引前先判断索引是否存在
DROP INDEX IF EXISTS sys_user_user_name_idx ON sys_user_t;
CREATE INDEX sys_user_user_name_idx ON sys_user_t (user_name);
- 执行DDL/DML脚本前,必须在测试环境验证通过,再在生产环境执行,执行前备份相关数据。
-
避免使用复杂数据库对象:尽量减少存储过程、函数、触发器的使用,复杂业务逻辑优先在应用层实现,降低数据库维护成本。
-
统一SQL格式化工具:开发团队使用统一的SQL格式化工具(如Navicat、DataGrip、DBeaver的格式化功能),保证SQL格式一致。
八、规范落地建议
-
团队培训:组织开发团队全员学习本规范,确保每位开发人员理解并掌握规范细节。
-
代码评审:将SQL规范纳入代码评审流程,评审不通过的SQL禁止上线。
-
定期复盘:定期排查生产环境慢查询日志,分析违规SQL案例,优化规范内容,持续提升开发团队SQL编写水平。
-
脚本目录管理:SQL操作脚本需按版本统一保留在对应版本的sql目录文件夹下,同时区分DDL(建库、建表、改表等)和DML(插入、更新、删除等)脚本存放,分别建立ddl和dml子目录,便于版本追溯和脚本管理。