阿里巴巴 Java 开发手册学习总结(三):MySQL 规约与工程规范
Published in:2025-06-28 | category: 数据库

本篇整理阿里 Java 开发手册中 MySQL 数据库规约和工程结构规范部分,重点是索引、SQL 写法和分层规范。

一、建表规约

三个必备字段

手册要求每张业务表都有:

1
2
3
id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'

create_timeupdate_time 是排查数据问题的最基础手段,缺了就没法知道数据什么时候写入、什么时候被改。

不允许外键

手册明确禁止在数据库层面定义外键(FOREIGN KEY)。原因:

  • 外键约束在高并发写入时会产生额外的锁竞争
  • 迁移和 DDL 变更时外键依赖关系复杂
  • 一致性改由应用层保证

字段不允许为 NULL(有默认值代替)

1
2
3
4
5
-- 错误
age INT,

-- 正确
age INT NOT NULL DEFAULT 0 COMMENT '年龄,0表示未知'

原因:NULL 值在索引、统计、判断时有很多反直觉的行为,比如 NULL != NULLCOUNT(列名) 不统计 NULL 等。


二、索引规约

业务唯一字段必须建唯一索引

1
2
-- 哪怕应用层已经有唯一校验,数据库层也要建唯一索引
UNIQUE INDEX uk_order_no (order_no)

应用层校验 + 数据库唯一索引是双保险:应用层做并发校验(先查后写之间有时间窗口),数据库做最终保障。

索引命名规范

类型命名格式示例
主键索引pk_字段名pk_id
唯一索引uk_字段名uk_order_no
普通索引idx_字段名idx_user_id

最左前缀原则

联合索引 (a, b, c),以下查询可以用到索引:

  • WHERE a = ?
  • WHERE a = ? AND b = ?
  • WHERE a = ? AND b = ? AND c = ?

以下无法用到索引:

  • WHERE b = ?(跳过了 a)
  • WHERE b = ? AND c = ?(跳过了 a)

建联合索引时,区分度高的字段放左边,查询条件最常用的字段放左边。

避免索引失效的常见写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 在索引列上做函数运算,索引失效
SELECT * FROM user WHERE DATE(create_time) = '2024-01-01';
-- 改成范围查询
SELECT * FROM user WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';

-- 隐式类型转换,索引失效
-- phone 是 VARCHAR,传了 INT
SELECT * FROM user WHERE phone = 13800000000;
-- 改成字符串
SELECT * FROM user WHERE phone = '13800000000';

-- like 前缀通配符,索引失效
SELECT * FROM user WHERE name LIKE '%张%';
-- 如果必须做全文模糊搜索,考虑 Elasticsearch

三、SQL 规约

SELECT 不要用 *

1
2
3
4
5
-- 错误
SELECT * FROM order WHERE user_id = ?;

-- 正确
SELECT id, order_no, amount, status FROM order WHERE user_id = ?;

原因:

  • SELECT * 会读取所有列,包括大字段(TEXT、BLOB),增加网络传输量
  • 表结构变化后,* 的含义悄悄改变,容易引入 bug
  • 无法使用覆盖索引优化

禁止超过 3 张表的 JOIN

超过 3 张表的 JOIN 优化器难以选择最优执行计划,且索引设计往往需要针对 JOIN 条件特殊处理。

解法:把 JOIN 拆成多次查询,在应用层组装。这也符合微服务架构下”跨服务数据不做 JOIN”的原则。

分页查询的深度分页问题

1
2
3
4
5
-- 当 offset 很大时,MySQL 仍然要扫描 offset+limit 行再丢弃前面的
SELECT * FROM order ORDER BY id LIMIT 1000000, 10; -- 慢

-- 改成游标分页(记录上次查询的最大 id)
SELECT * FROM order WHERE id > #{lastId} ORDER BY id LIMIT 10; -- 快

四、工程结构规范

分层职责

手册定义的标准分层:

1
2
3
4
Controller  ← 参数校验、协议转换(HTTP/RPC),不写业务逻辑
Service ← 业务逻辑,可以调用多个 Manager 和 DAO
Manager ← 通用业务处理层,封装第三方调用、缓存、DAO 组合
DAO/Mapper ← 数据访问,只做 SQL,不写业务判断

超过 5 个参数用 DTO 封装

1
2
3
4
5
// 错误:参数列表过长,调用时容易搞错顺序
UserService.createUser(String name, Integer age, String phone, String email, Integer status);

// 正确:封装为 DTO
UserService.createUser(CreateUserDTO dto);

Service 和 ServiceImpl 分离

手册要求:

  • UserService(接口)放在 service
  • UserServiceImpl(实现)放在 service.impl

好处:便于 Mock 测试,便于多实现切换(如 A/B 两套实现)。


小结

手册的 MySQL 规约背后都有实际的性能或稳定性原因,不是为了规范而规范。理解了为什么,比死记规则更有价值:外键禁止是因为锁竞争,SELECT * 禁止是因为性能和可维护性,深度分页改游标是因为全表扫描。

至此阿里 Java 开发手册三篇整理完成,覆盖了编程规约、并发异常、MySQL 和工程结构四个核心模块。

Prev:
《代码整洁之道》读书笔记
Next:
阿里巴巴 Java 开发手册学习总结(二):并发、异常与日志