当前位置: 首页 > article >正文

Java开发经验——阿里巴巴编码规范实践解析7

摘要

本文主要解析了阿里巴巴 Java 开发中的 SQL 编码规范,涉及 SQL 查询优化、索引建立、字符集选择、分页查询处理、外键与存储过程的使用等多个方面,旨在帮助开发者提高代码质量和数据库操作性能,避免常见错误和性能陷阱。

1. 【强制】业务上具有唯一特性的字段,即使是组合字段, 也必须建成唯一索引。

说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

  1. 唯一索引的作用:唯一索引能够保证数据库层面数据的唯一性约束,防止重复数据的插入,确保数据一致性和业务正确性。
  2. 为什么不仅靠应用层校验?:应用层校验可能因并发、网络异常、程序bug等原因出现遗漏,导致重复数据写入。如果没有数据库唯一索引,脏数据(重复、冲突)就难以避免。
  3. 性能考虑:虽然唯一索引会稍微增加写入时的开销,但通常这种开销是微乎其微的,远远小于因数据重复引发的业务混乱和数据清理成本。
  4. 组合唯一索引:当唯一约束不是单个字段,而是多个字段的组合时,也必须在数据库层创建组合唯一索引,保证这组字段的联合唯一性。

假设系统中有一张用户表 user,要求用户的手机号 phone 是唯一的:

CREATE TABLE user (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,phone VARCHAR(20) NOT NULL,username VARCHAR(50),...UNIQUE KEY uk_phone (phone)
);

即使应用层每次插入数据时都校验手机号是否存在,还是必须在数据库上建立唯一索引 uk_phone 来防止脏数据产生。再举个组合唯一索引的例子:订单系统中,user_idorder_no 组合必须唯一:

CREATE TABLE orders (id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,user_id BIGINT UNSIGNED NOT NULL,order_no VARCHAR(50) NOT NULL,amount DECIMAL(10, 2),...UNIQUE KEY uk_user_order (user_id, order_no)
);

这样确保同一个用户的订单号不重复。

总结

  • 唯一索引保证数据库层面数据唯一性,是数据质量保障的最后防线。
  • 不要只依赖应用层校验,避免因脏数据导致后续业务和数据分析混乱。
  • 即使性能开销存在,也远远小于维护脏数据的成本。

2. 【强制】超过三个表禁止 join。 需要 join 的字段,数据类型保持绝对一致; 多表关联查询时, 保证被关联的字段需要有索引。

说明:即使双表 join 也要注意表索引、SQL 性能。

  • 限制多表 Join(超过三个表禁止):多表 join 会导致 SQL 查询复杂度显著增加,影响数据库性能和响应时间。超过三张表的 join,尤其是在大数据量环境下,容易导致查询效率低下、锁表、内存消耗高等问题。
  • 保持 join 字段数据类型一致:如果 join 字段的数据类型不一致,数据库执行时会进行隐式类型转换,导致索引失效,查询性能严重下降,甚至出错。
  • 关联字段必须有索引:索引是数据库快速定位数据的关键。没有索引,join 查询会变成全表扫描,性能极差,特别是数据量大的情况下。
  • 即使双表 join 也要关注索引:不是说只有多表 join 才影响性能,双表 join 如果没有索引,同样可能导致慢查询。

假设有三个表:ordersusersproducts,进行关联查询:

sql复制编辑
SELECT o.id, u.username, p.product_name
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id
WHERE o.status = 1;

要求:

  • orders.user_idusers.id 的数据类型完全相同(例如都是 BIGINT UNSIGNED)。
  • orders.product_idproducts.id 的数据类型也完全相同。
  • users.idproducts.id 都是主键,天然有索引。
  • orders.user_idorders.product_id 应该建立普通索引(如果业务查询频繁)。

如果要多于三个表关联,比如加上 order_details,就要考虑是否能拆分查询或优化,否则禁止这么做。

可能的优化建议:

  • 如果业务需要超过三个表关联,尽量拆分查询,或者做缓存处理。
  • 保证所有 join 字段都建索引。
  • 严格检查字段类型,避免隐式转换导致索引失效。
  • 使用 SQL 执行计划分析(EXPLAIN)查看 join 是否走索引。

3. 【强制】在 varchar 字段上建立索引时, 必须指定索引长度, 没必要对全字段建立索引, 根据实际文本区分度决定索引长度。

说明: 索引的长度与区分度是一对矛盾体, 一般对字符串类型数据, 长度为 20 的索引, 区分度会高达 90%以上,可以使用 count(distinct left(列名,索引长度)) / count(*) 的区分度来确定。

4. 【强制】页面搜索严禁左模糊或者全模糊, 如果需要请走搜索引擎来解决。

说明:索引文件具有 B-Tree 的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

5. 【推荐】如果有 order by 的场景, 请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 filesort 的情况,影响查询性能。

正例:where a = ? and b = ? order by c;索引:a_b_c

反例:索引如果存在范围查询,那么索引有序性无法利用,如:WHERE a > 10 ORDER BY b;索引 a_b 无法排序。

在执行带有 ORDER BY 的查询时,MySQL 会尝试利用索引的有序性来避免额外的排序(filesort),从而提升查询性能。

5.1.1. 关键点总结

  1. 索引的顺序决定排序能否利用索引:组合索引(如 (a, b, c))是有顺序的,MySQL只能根据索引前缀部分利用有序性。
  2. ORDER BY 的字段必须是索引的连续后缀,且放在索引的最后
    • 比如索引 (a, b, c)WHERE a=? AND b=? ORDER BY c 可以利用索引避免 filesort。
    • 这是因为 abWHERE 过滤且是等值条件,索引顺序完整,c 排序可以直接利用索引。
  1. 范围查询打断索引有序性
    • 如果 WHERE 条件中出现范围查询(如 a > 10),索引在这个字段之后的顺序无法被利用。
    • 例子:WHERE a > 10 ORDER BY b,索引 (a, b) 无法利用索引顺序进行 ORDER BY b,导致 filesort。
  1. 避免 filesort 影响性能:filesort 是MySQL的额外排序操作,会增加磁盘IO和CPU开销。利用好索引顺序可避免。

5.1.2. 举例说明

查询

索引

是否能避免 filesort?

原因

WHERE a = ? AND b = ? ORDER BY c

(a, b, c)

等值条件过滤,索引有序可直接排序

WHERE a > 10 ORDER BY b

(a, b)

范围查询 a > 10

破坏索引有序,无法用索引排序

WHERE a = ? ORDER BY b

(a, b)

a

为等值条件,b

索引顺序可用

WHERE a = ? ORDER BY c

(a, b, c)

b

被跳过,索引顺序断裂

5.1.3. 优化建议

  • 设计索引时,考虑查询中 WHEREORDER BY 的字段顺序,尽量让等值过滤字段排在前面,排序字段紧随其后。
  • 避免在索引的前缀字段上使用范围查询,否则后续字段的排序将无法利用索引。
  • 结合 EXPLAIN 分析查询计划,确认是否出现了 Using filesort,及时调整索引。

6. 【推荐】利用覆盖索引来进行查询操作, 避免回表。

说明:如果一本书需要知道第 11 章是什么标题,会翻开第 11 章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。

正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用 explain的结果,extra 列会出现:using index。

覆盖索引是指:查询的所有列(SELECT、WHERE、ORDER BY 中涉及的列)都在同一个索引中,MySQL 无需回表即可返回结果。

6.1.1. 回表与覆盖索引的区别

类型

行为描述

性能影响

回表查询

先用二级索引定位主键,再去主键索引找完整记录

需要更多I/O,性能较低

覆盖索引查询

所有字段直接在索引中拿到,无需访问主表

性能更高,I/O更少

6.1.2. 覆盖索引的判断方式

使用 EXPLAIN 查看执行计划时,Extra出现:

  • Using index(使用了覆盖索引)
    Using where; Using index(部分使用索引,但仍可能回表)

6.1.3. 举例说明

表结构:

CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(50),age INT,email VARCHAR(50),INDEX idx_name_age (name, age)
);

覆盖索引查询(不回表):

SELECT name, age FROM user WHERE name = 'Tom';
-- ✅ name 和 age 都在 idx_name_age 中,形成覆盖索引

回表查询(需要访问主表):

SELECT email FROM user WHERE name = 'Tom';
-- ❌ email 不在 idx_name_age 中,需通过主键回表获取 email

6.1.4. 优化建议

  • SELECT 尽量只取必要字段,避免使用 SELECT *,更容易利用覆盖索引。
  • 组合索引包含 SELECT 和 WHERE 字段,能最大化覆盖索引的利用。
  • 覆盖索引尤其适用于:高频读操作 + 查询字段固定少量 + 读性能要求高 的场景。

7. 【推荐】利用延迟关联或者子查询优化超多分页场景。

说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。

正例:先快速定位需要获取的 id 段,然后再关联:
SELECT t1.* FROM 表 1 as t1 , (select id from 表 1 where 条件 LIMIT 100000 , 20) as t2 where t1.id = t2.id

你提到的这条优化建议是大分页性能优化中非常重要的一种方式,适用于 LIMIT offset, N 中 offset 非常大的情况。下面我将从原理、适用场景、示例 SQL、数据库设计时的思考等方面进行深入讲解,帮助你真正理解并能在系统设计中灵活运用。

7.1. ✅ 核心问题:大 offset 分页效率低

在 MySQL 中,分页语句如:

SELECT * FROM 表 WHERE 条件 LIMIT 100000, 20;

MySQL 的执行方式是:

  1. 扫描前 100020 条记录
  2. 丢弃前 100000 条,仅返回最后的 20 条

📌 当 offset 非常大(例如上万行),MySQL 会浪费大量资源扫描无用数据,从而导致严重的性能问题。

7.2. ✅ 优化方案:延迟关联 / 子查询方式

思路:先用一个子查询只查 id,快速定位目标记录,再用主查询根据 id 执行 精确关联查询

7.2.1. 🔍 示例 SQL(延迟关联方式)

-- 子查询快速定位分页 id
SELECT t1.*
FROM 表 AS t1
JOIN (SELECT idFROM 表WHERE 条件ORDER BY idLIMIT 100000, 20
) AS t2 ON t1.id = t2.id;

📌 优点:

  • 子查询只处理 id 字段,数据量少,扫描快。
  • 主查询通过 id 精准获取数据,不受 offset 大小影响。

7.3. ✅ 适用场景

  • 高频访问的大分页列表(如历史数据、日志、交易明细)。
  • 用户下拉加载下一页数据(cursor 模式更佳)。
  • 分页数据量巨大(超过 1 万行以上)。

7.4. ✅ 数据库设计时的思考方式

设计数据库和索引时,如果预期存在大量分页跳转需求,可以考虑:

❗避免盲目使用 LIMIT offset, size

  • 对于大数据量分页,应使用“基于游标的分页”或“延迟关联”。

✅ 分页基准字段要建索引

  • 子查询中 ORDER BY idWHERE 条件 中涉及的字段应该建立组合索引。

✅ id 或排序字段设计应具备可预测性(如自增、时间戳)

  • 有助于实现“基于最后一条记录”的分页(cursor-based pagination)。

7.5. ✅ 进一步优化方式(基于游标分页)

这是延迟关联的终极形式,适用于用户只“向后翻页”的场景:

-- 使用上一次查询的最大 ID 作为游标
SELECT *
FROM 表
WHERE id > 上一次最大 id
ORDER BY id
LIMIT 20;

👍 优点:

  • 不依赖 offset,查询永远是 LIMIT N,性能稳定。
  • 前提是 id 单调递增,且分页顺序与 id 保持一致。

7.6. 总结一句话:

大分页千万不能硬跳 offset,延迟关联或游标分页是优化之道。

📌 记住分页优化的三个“不要”:

  • 不要在大 offset 上直接分页;
  • 不要 SELECT *(避免回表);
  • 不要忽略索引对排序字段的支持。

8. 【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 const 最好。

说明:

  1. consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
  2. ref 指的是使用普通的索引(normal index)。
  3. range 对索引进行范围检索。

反例:explain 表的结果,type = index,索引物理文件全扫描,速度非常慢,这个 index 级别比较 range 还低,与全表扫描是小巫见大巫。

9. 【推荐】建组合索引的时候,区分度最高的在最左边。

正例:如果 where a = ? and b = ?,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即可。

说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where c > ? and d = ? 那么即使c 的区分度更高,也必须把 d 放在索引的最前列,即建立组合索引 idx_d_c。

10. 【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。

11. 【参考】创建索引时避免有如下极端误解:

  1. 索引宁滥勿缺。认为一个查询就需要建一个索引。
  2. 吝啬索引的创建。认为索引会消耗空间、严重拖慢记录的更新以及行的新增速度。
  3. 抵制唯一索引。认为唯一索引一律需要在应用层通过“先查后插”方式解决。

12. 【强制】不要使用 count(列名) 或 count(常量) 来替代 count(*),count(*) 是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。

说明:count(*) 会统计值为 NULL 的行,而 count(列名) 不会统计此列为 NULL 值的行。

12.1. ✅ 语义区别:COUNT(*)COUNT(列名) 根本不同

方式

统计内容

是否统计 NULL

COUNT(*)

统计 所有行数(包含 NULL)

✅ 是

COUNT(列名)

统计该列 非 NULL 的行数

❌ 否

COUNT(1)

COUNT(常量)

COUNT(*)

一样(MySQL特性)但不标准

✅ 是

12.2. ✅ SQL92 标准推荐使用 COUNT(*)

它表示“行级别计数”,而非某个列的计数,是最安全、语义最明确、兼容性最好的写法。

12.3. ✅ 示例对比

假设有如下数据表:

CREATE TABLE user (id INT PRIMARY KEY,name VARCHAR(50),email VARCHAR(100)
);INSERT INTO user (id, name, email) VALUES
(1, 'Tom', 'tom@example.com'),
(2, 'Jerry', NULL),
(3, 'Bob', 'bob@example.com');

12.3.1. 使用 COUNT(*)

SELECT COUNT(*) FROM user;
-- 返回结果:3

✅ 所有行都统计,无论 email 是否为 NULL。

12.3.2. 使用 COUNT(email)

sql复制编辑
SELECT COUNT(email) FROM user;
-- 返回结果:2

❌ 只有 email 不为 NULL 的两条数据被统计。

12.3.3. 使用 COUNT(1)COUNT('x')

SELECT COUNT(1) FROM user;
-- 返回结果:3SELECT COUNT('abc') FROM user;
-- 返回结果:3

✅ 和 COUNT(*) 在 MySQL 下等价,但并不是标准写法,可读性差,不推荐。

12.4. ✅ 三、为什么推荐强制使用 COUNT(*)

  • 语义最清晰:表示“统计总行数”,没有歧义。
  • 不会遗漏 NULL 行:在数据质量不一致时,避免错误理解。
  • 最具通用性:SQL92 标准定义,各数据库平台支持度最高。
  • COUNT(列名) 容易被误用,统计结果可能出错(尤其在报表场景)。

12.5. ✅ 数据库设计/开发中的实践建议

场景

推荐使用方式

统计表的总记录数

COUNT(*)

判断某字段是否有非空数据的记录数

COUNT(列名)

多表关联后用于计数逻辑

COUNT(*)

ORM 框架中用 count 查询

显式指定 *,避免误用其他字段

总结一句话:COUNT(*) 是最安全的计数方式,应作为默认使用。除非你明确知道自己只统计非 NULL 字段。

13. 【强制】count(distinct col) 计算该列除 NULL 之外的不重复行数,注意 count(distinct col1 , col2) 如果其中一列全为 NULL,那么即使另一列有不同的值,也返回为 0。

14. 【强制】当某一列的值全是 NULL 时,count(col) 的返回结果为 0;但 sum(col) 的返回结果为 NULL,因此使用 sum() 时需注意 NPE 问题。

正例:可以使用如下方式来避免 sum 的 NPE 问题:SELECT IFNULL(SUM(column) , 0) FROM table;

你这条规范非常重要,特别是在财务统计、数据报表等场景中,SUM(NULL) ≠ 0 常常是导致空指针异常(NPE)或业务逻辑错误的隐性原因。

14.1. ✅ COUNT(col)SUM(col) 的行为差异

函数

NULL 全部出现时的行为

是否易出错

原因说明

COUNT(col)

返回 0

安全

忽略 NULL,返回行数为 0

SUM(col)

返回 NULL

危险

无法参与求和,返回 NULL(不是 0)

14.2. ✅ 案例对比

假设表 transaction

id | amount
---|--------
1  | NULL
2  | NULL

14.2.1. COUNT(amount)

SELECT COUNT(amount) FROM transaction;
-- 结果:0 ✅

14.2.2. SUM(amount)

SELECT SUM(amount) FROM transaction;
-- 结果:NULL ❌(不是 0)

在 Java、Python、Go、JS 等语言中将 NULL 映射为数值时,往往无法自动转换为 0,会触发异常或逻辑错误。

14.3. ✅ NPE 避坑推荐写法


-- 避免 null 问题,强制设定默认值为 0
SELECT IFNULL(SUM(amount), 0) AS total_amount
FROM transaction;

其他数据库中的写法(等价):

数据库

函数

MySQL

IFNULL(SUM(col), 0)

PostgreSQL

COALESCE(SUM(col), 0)

Oracle

NVL(SUM(col), 0)

SQL Server

ISNULL(SUM(col), 0)

14.4. ✅ 为什么这点在系统设计中很重要?

  • 业务误差:财务类、积分类、流量类报表中,“空”不能代表 0,会导致账目对不上。
  • 程序崩溃:后端取数据库结果为 NULL,没有判断就使用 .intValue() 或加法,容易出现 NPE。
  • 前端展示异常:如果传回 NULL,不做处理显示为“空白”,用户体验差。

15. 【强制】使用 ISNULL() 来判断是否为 NULL 值。

说明:NULL 与任何值的直接比较都为 NULL。

  1. NULL<>NULL 的返回结果是 NULL,而不是 false。
  2. NULL=NULL 的返回结果是 NULL,而不是 true。
  3. NULL<>1 的返回结果是 NULL,而不是 true。

反例:在 SQL 语句中,如果在 null 前换行,影响可读性。

select * from table where column1 is null and column3 is not null;而 ISNULL(column) 是一个整体,简洁易懂。从性能数据上分析,ISNULL(column) 执行效率更快一些。

16. PageHelperLIMIT 是 MySQL 分页中常见的两种实现方式?

16.1. 基本定义

名称

类型

简介

LIMIT

SQL语法

原生 MySQL 分页语法,形式为 LIMIT offset, size

PageHelper

Java插件(MyBatis)

第三方分页插件,自动拦截 MyBatis 的查询语句,自动拼接分页逻辑

16.2. 使用方式对比

LIMIT 分页示例(SQL层)

SELECT * FROM user ORDER BY id LIMIT 20, 10;
-- 表示从第 21 条开始,取 10 条

通常你需要自己手动计算 offset:

int offset = (pageNum - 1) * pageSize;

16.3. ✅ PageHelper 分页示例(Java层)

PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectAll(); // 会自动拼接 LIMIT 分页 SQL

PageHelper 会自动:

  • 计算 offset
  • 拼接 LIMIT
  • 自动执行 SELECT COUNT(*) 获取总条数(可配置)

16.4. ✅ 核心区别

项目

PageHelper

LIMIT

作用层级

Java 代码层(MyBatis 插件)

SQL 层(数据库原生)

是否自动分页

✅ 自动拦截并分页

❌ 需要自己写分页 SQL

是否自动统计总条数

✅ 自动执行 count 查询(可关闭)

❌ 需要手动写 count SQL

使用成本

高:需要引入依赖、使用特定 API

低:只用 SQL 即可

控制灵活性

中:依赖框架,粒度有限

高:SQL 自定义能力强

性能优化空间

中:依赖插件逻辑

高:可配合子查询、索引优化等

16.5. ✅ 选择建议

场景

建议方式

使用 MyBatis + 快速开发项目

✅ 推荐 PageHelper

数据量大、分页逻辑复杂、需要优化极致性能

✅ 推荐手写 LIMIT

使用其他 ORM(如 JPA、Hibernate)

❌ PageHelper 不适用

与前端对接灵活分页(如游标分页、延迟关联等)

✅ 手写 LIMIT 更灵活

16.6. ✅ 性能提示

  • LIMIT offset, size 在 offset 很大时会性能下降(PageHelper 也会遇到相同问题)。
  • PageHelper 的 PageHelper.startPage()必须在查询语句前调用,否则不会生效。
  • PageHelper 可配置是否执行 count 查询(在某些场景可以关闭提高性能)。

PageHelper 是 LIMIT 的封装和增强,用于 Java 层 MyBatis 自动分页,适合快速开发;而 LIMIT 是底层 SQL 原语,适合需要高性能、复杂控制的分页场景。

17. 【强制】代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。

17.1. 规则含义:为什么 count = 0 要直接返回?

分页查询一般有两步:

  1. 先查总数:执行 SELECT COUNT(*) FROM ...
  2. 再查分页数据:执行 SELECT * FROM ... LIMIT offset, size

当第一步 count = 0,说明根本就没有数据,第二步分页语句毫无意义,却仍会执行:

  • 产生不必要的数据库连接与查询压力
  • 增加网络 I/O 与序列化成本
  • 增加代码复杂性(后续还得处理空结果)

17.2. ✅ 正确做法(Java 示例,以 MyBatis + PageHelper 为例)

int total = userMapper.countByCondition(condition);
if (total == 0) {return PageResult.empty(); // 或 return Collections.emptyList();
}PageHelper.startPage(pageNum, pageSize);
List<User> users = userMapper.selectByCondition(condition);
return new PageResult<>(total, users);

17.3. ✅ 推荐封装分页方法:

public <T> PageResult<T> doPageQuery(Supplier<List<T>> dataQuery, Supplier<Integer> countQuery) {int total = countQuery.get();if (total == 0) {return new PageResult<>(0, Collections.emptyList());}List<T> list = dataQuery.get();return new PageResult<>(total, list);
}

17.4. ✅ 示例说明(分页优化对比)

场景

非优化做法

优化后做法

count = 0

执行 2 条 SQL,第二条 LIMIT 语句返回空结果

只执行 1 条 COUNT 语句

count > 0

正常执行分页查询

正常执行分页查询

总结一句话:分页查询必须先查总数,若为 0 则立刻返回,避免无意义的分页 SQL 查询。

18. 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。

说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的

student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。

18.1. ✅ 外键与级联的概念

外键(Foreign Key)

  • 表 A 的字段指向表 B 的主键。
  • 用于保证数据引用的完整性
  • 可配置 级联更新(ON UPDATE CASCADE)级联删除(ON DELETE CASCADE)

例子:

-- 成绩表成绩关联学生表 student_id 外键
ALTER TABLE score ADD CONSTRAINT fk_student_id FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE;

18.2. ❌ 为什么在分布式/高并发环境下禁用外键与级联?

问题类别

原因

✅ 高耦合

表之间强关联,修改主表字段会影响多个从表,破坏模块边界。

❌ 可用性风险

外键错误会导致插入失败,不能插入“暂时孤儿数据”(先插从表再插主表)。

❌ 性能问题

插入/更新时需实时检查外键约束,降低写入性能

❌ 阻塞与锁冲突

级联更新/删除涉及多个表,事务大、锁多,容易引发阻塞与死锁

❌ 不适合分布式

分布式中不同表可能分库分表,外键无法跨节点生效。

❌ 数据迁移困难

有外键约束的数据不方便导入导出、数据同步时容易失败。

18.3. ✅ 最佳实践:在应用层维护逻辑外键

把数据库层的“关系约束”转为应用层的“逻辑约束”

✅ 正例:学生和成绩表设计

-- student 表
CREATE TABLE student (id BIGINT PRIMARY KEY,name VARCHAR(50)
);-- score 表(没有外键约束)
CREATE TABLE score (id BIGINT PRIMARY KEY,student_id BIGINT, -- 虽然逻辑上关联 student.id,但无外键score INT
);

应用层约束方式:

  • 插入成绩前,先检查学生是否存在。
  • 删除学生时,先显式删除成绩。
  • 通过数据库唯一索引 + 应用校验,避免脏数据。

18.4. ✅ 如何在应用层实现级联逻辑

示例:删除学生时删除对应成绩

@Transactional
public void deleteStudent(Long studentId) {
scoreMapper.deleteByStudentId(studentId); // 先删子表
studentMapper.deleteById(studentId);      // 再删主表
}

这样你就掌握了级联逻辑的顺序和可控性,可避免数据库层隐性操作带来的性能和一致性风险。

总结:外键和级联 = 数据库强耦合,适合低并发内网系统;应用层维护关系 = 弱耦合+高性能,适合分布式与互联网架构。

19. 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。

20. 【强制】数据订正(特别是删除或修改记录操作)时,要先 select,避免出现误删除的情况,确认无误才能执行更新语句。

21. 【强制】对于数据库中表记录的查询和变更,只要涉及多个表,都需要在列名前加表的别名(或表名)进行限定。

说明:对多表进行查询记录、更新记录、删除记录时,如果对操作列没有限定表的别名(或表名),并且操作列在多个表中存在时,就会抛异常。

正例:select t1.name from first
table as t1 , second
table as t2 where t1.id = t2.id;

反例:在某业务中,由于多表关联查询语句没有加表的别名(或表名)的限制,正常运行两年后,最近在某个表中增加一个同名字段,在预发布环境做数据库变更后,线上查询语句出现出 1052 异常:Column 'name' infield list is ambiguous。

22. 【推荐】SQL 语句中表的别名前加 as,并且以 t1、t2、t3、...的顺序依次命名。

说明:

  1. 别名可以是表的简称,或者是依照表在 SQL 语句中出现的顺序,以 t1、t2、t3 的方式命名。
  2. 别名前加 as 使别名更容易识别。

正例:

select t1.name from first
table as t1 , second
table as t2 where t1.id = t2.id;

23. 【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在1000 个之内。

23.1. 规范原文理解

IN (...) 操作符适用于小数据量的集合匹配,但当集合过大(> 1000 个元素)时,容易导致性能下降、SQL 解析异常甚至执行失败。因此应尽量避免,实在无法避免时必须控制集合数量

23.2. IN 的风险与问题点

问题类型

原因说明

❌ SQL 长度超限

SQL 文本过长会超过数据库语法限制(如 Oracle 限制 1000 个 in 参数)

❌ 查询计划复杂

IN 集合过大,数据库生成执行计划开销大,执行效率低

❌ 命中索引差

索引优化器难以对大 IN 集合选择最佳执行路径,导致无法高效命中索引

❌ 安全隐患

大量拼接 IN (...) 参数易引发 SQL 注入和执行失败

24. 【参考】因国际化需要, 所有的字符存储与表示,均采用 utf8mb4 字符集,字符计数方法需要注意。

说明:

SELECT LENGTH("轻松工作");--返回为 12
SELECT CHARACTER_LENGTH("轻松工作");--返回为 4
表情需要用 utf8mb4 来进行存储,注意它与 utf8 编码的区别。

25. 【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE无事务且不触发 trigger,有可能造成事故,故不建议在开发代码中使用此语句。

说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。

博文参考

《阿里java规范》

相关文章:

Java开发经验——阿里巴巴编码规范实践解析7

摘要 本文主要解析了阿里巴巴 Java 开发中的 SQL 编码规范&#xff0c;涉及 SQL 查询优化、索引建立、字符集选择、分页查询处理、外键与存储过程的使用等多个方面&#xff0c;旨在帮助开发者提高代码质量和数据库操作性能&#xff0c;避免常见错误和性能陷阱。 1. 【强制】业…...

权威认证与质量保障:第三方检测在科技成果鉴定测试中的核心作用

科技成果鉴定测试是衡量科研成果技术价值与应用潜力的关键环节&#xff0c;其核心目标在于通过科学验证确保成果的可靠性、创新性和市场适配性。第三方检测机构凭借其独立性、专业性和权威性&#xff0c;成为科技成果鉴定测试的核心支撑主体。本文从测试流程、第三方检测的价值…...

混和效应模型在医学分析中的应用

混合效应模型&#xff08;Mixed Effects Model&#xff09;&#xff0c;又称多层模型或随机效应模型&#xff0c;因其能同时分析固定效应&#xff08;群体平均趋势&#xff09;和随机效应&#xff08;个体或组间差异&#xff09;&#xff0c;在医学研究中广泛应用于处理具有层次…...

架构分享|三层存储架构加速云端大模型推理

作者简介 Nilesh Agarwal,Inferless 联合创始人&CTO 关于Inferless Inferless &#xff1a;无服务器 GPU 推理无需管理服务器即可扩展机器学习推理&#xff0c;轻松部署复杂的自定义模型。获得Sequoia、Antler 和 Blume Ventures 的支持。 大语言模型&#xff08;LLM&a…...

Perforce P4产品简介:无限扩展+全球协作+安全管控+工具集成(附下载)

本产品简介由Perforce中国授权合作伙伴——龙智编辑整理&#xff0c;旨在带您快速了解Perforce P4版本控制系统的强大之处。 世界级无限可扩展的版本控制系统 Perforce P4&#xff08;原Helix Core&#xff09;是业界领先的版本控制平台&#xff0c;备受19家全球Top20 AAA级游…...

网络协议入门:TCP/IP五层模型如何实现全球数据传输?

&#x1f50d; 开发者资源导航 &#x1f50d;&#x1f3f7;️ 博客主页&#xff1a; 个人主页&#x1f4da; 专栏订阅&#xff1a; JavaEE全栈专栏 内容&#xff1a; 网络初识什么是网络&#xff1f;关键概念认识协议五元组 协议分层OSI七层模型TCP/IP五层&#xff08;四层&…...

Docker安装Redis集群(3主3从+动态扩容、缩容)保姆级教程含踩坑及安装中遇到的问题解决

前言 部署集群前&#xff0c;我们需要先掌握Redis分布式存储的核心算法。了解这些算法能帮助我们在实际工作中做出合理选择&#xff0c;同时清晰认识各方案的优缺点。 一、分布式存储算法 我们通过一道大厂面试题来进行阐述。 如下&#xff1a;1-2亿条数据需要缓存&#xff…...

企业级 AI 开发新范式:Spring AI 深度解析与实践

一、Spring AI 的核心架构与设计哲学 1.1 技术定位与价值主张 Spring AI 作为 Spring 生态系统的重要组成部分&#xff0c;其核心使命是将人工智能能力无缝注入企业级 Java 应用。它通过标准化的 API 抽象和 Spring Boot 的自动装配机制&#xff0c;让开发者能够以熟悉的 Spr…...

如何用docker部署ELK?

环境&#xff1a; ELK 8.8.0 Ubuntu20.04 问题描述&#xff1a; 如何用docker部署ELK&#xff1f; 解决方案&#xff1a; 一、环境准备 &#xff08;一&#xff09;主机设置 安装 Docker Engine &#xff1a;版本需为 18.06.0 或更新。可通过命令 docker --version 检查…...

Redis最佳实践——安全与稳定性保障之高可用架构详解

全面详解 Java 中 Redis 在电商应用的高可用架构设计 一、高可用架构核心模型 1. 多层级高可用体系 #mermaid-svg-Ffzq72Onkv7wgNKQ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Ffzq72Onkv7wgNKQ .error-icon{f…...

【Python 算法零基础 4.排序 ⑥ 快速排序】

既有锦绣前程可奔赴&#xff0c;亦有往日岁月可回首 —— 25.5.25 选择排序回顾 ① 遍历数组&#xff1a;从索引 0 到 n-1&#xff08;n 为数组长度&#xff09;。 ② 每轮确定最小值&#xff1a;假设当前索引 i 为最小值索引 min_index。从 i1 到 n-1 遍历&#xff0c;若找到…...

Java面试实战:从Spring Boot到微服务与AI的全栈挑战

场景一&#xff1a;初步了解和基本技术问题 面试官&#xff1a;我们先从基础开始&#xff0c;谢先生&#xff0c;你能简单介绍一下你在Java SE上的经验吗&#xff1f; 谢飞机&#xff1a;当然&#xff01;Java就像是我的老朋友&#xff0c;尤其是8和11版本。我用它们做过很多…...

Go 即时通讯系统:日志模块重构,并从main函数开始

重构logger 上次写的logger.go过于繁琐&#xff0c;有很多没用到的功能&#xff1b;重构后只提供了简洁的日志接口&#xff0c;支持日志轮转、多级别日志记录等功能&#xff0c;并采用单例模式确保全局只有一个日志实例 全局变量 var (once sync.Once // 用于实现…...

CppCon 2014 学习:Exception-Safe Coding

以下是你提到的内容&#xff08;例如 “Exception-Safe Coding 理解” 和 “Easier to Read!” 等&#xff09;翻译成中文并进一步解释&#xff1a; 承诺&#xff1a;理解异常安全&#xff08;Exception-Safe Coding&#xff09; 什么是异常安全&#xff1f; 异常安全是指&a…...

MYSQL MGR高可用

1&#xff0c;MYSQL MGR高可用是什么 简单来说&#xff0c;MySQL MGR 的核心目标就是&#xff1a;确保数据库服务在部分节点&#xff08;服务器&#xff09;发生故障时&#xff0c;整个数据库集群依然能够继续提供读写服务&#xff0c;最大限度地减少停机时间。 2. 核心优势 v…...

阿里通义实验室突破空间音频新纪元!OmniAudio让360°全景视频“声”临其境

在虚拟现实和沉浸式娱乐快速发展的今天&#xff0c;视觉体验已经远远不够&#xff0c;声音的沉浸感成为打动用户的关键。然而&#xff0c;传统的视频配音技术往往停留在“平面”的音频层面&#xff0c;难以提供真正的空间感。阿里巴巴通义实验室&#xff08;Qwen Lab&#xff0…...

异步上传石墨文件进度条前端展示记录(采用Redis中String数据结构实现-苏东坡版本)

昔者&#xff0c;有客临门&#xff0c;亟需自石墨文库中撷取卷帙若干。此等文册&#xff0c;非止一卷&#xff0c;乃累牍连篇&#xff0c;亟需批量转置。然吾辈虑及用户体验&#xff0c;当效东坡"腹有诗书气自华"之雅意&#xff0c;使操作如行云流水&#xff0c;遂定…...

处理知识库文件_编写powershell脚本文件_批量转换其他格式文件到pdf文件---人工智能工作笔记0249

最近在做部门知识库&#xff0c;选用的dify&#xff0c;作为rag的工具&#xff0c;但是经过多个对比&#xff0c;最后发现&#xff0c; 比较好用的是&#xff0c;纳米搜索&#xff0c;但是可惜纳米搜索无法在内网使用&#xff0c;无法把知识库放到本地&#xff0c;导致 有信息…...

rtpmixsound:实现音频混音攻击!全参数详细教程!Kali Linux教程!

简介 一种将预先录制的音频与指定目标音频流中的音频&#xff08;即 RTP&#xff09;实时混合的工具。 一款用于将预先录制的音频与指定目标音频流中的音频&#xff08;即 RTP&#xff09;实时混合的工具。该工具创建于 2006 年 8 月至 9 月之间。该工具名为 rtpmixsound。它…...

【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder

目录 如何使用&#xff1f; 1. 示例代码&#xff08;基于Netty&#xff09; 2. 关键参数解释 3. 协议格式示例 4. 常见配置场景 场景1&#xff1a;长度字段包含自身 场景2&#xff1a;长度字段在消息中间 5. 注意事项 举个例子 完整示例&#xff1a;客户端与服务端交互…...

stm与51单片机哪个更适合新手学

一句话总结 51单片机&#xff1a;像学骑自行车&#xff0c;简单便宜&#xff0c;但只能在小路上骑。 STM32&#xff1a;像学开汽车&#xff0c;复杂但功能强&#xff0c;能上高速公路&#xff0c;还能拉货载人&#xff08;做复杂项目&#xff09;。 1. 为啥有人说“先学51单片…...

【计算机网络】第3章:传输层—面向连接的传输:TCP

目录 一、PPT 二、总结 TCP&#xff08;传输控制协议&#xff09;详解 1. 概述 核心特性&#xff1a; 2. TCP报文段结构 关键字段说明&#xff1a; 3. TCP连接管理 3.1 三次握手&#xff08;建立连接&#xff09; 3.2 四次挥手&#xff08;终止连接&#xff09; 4. 可…...

从架构视角设计统一网络请求体系 —— 基于 uni-app 的前后端通信模型

在使用 uni-app 开发跨平台应用时&#xff0c;设计一套清晰、统一、可扩展的网络请求模块 是前期架构的关键环节。良好的请求模块不仅提高开发效率&#xff0c;更是保证后期维护、调试和业务扩展的基础。 一、网络请求设计目标 在uni-app中设计网络请求模块&#xff0c;应遵循…...

《信号与系统》--期末总结V1.0

《信号与系统》–期末总结V1.0 学习链接 入门&#xff1a;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;_哔哩哔哩_bilibili 精通&#xff1a;2022浙江大学信号与系统&#xff08;含配…...

第32次CCF计算机软件能力认证-2-因子化简

因子化简 刷新 时间限制&#xff1a; 2.0 秒 空间限制&#xff1a; 512 MiB 下载题目目录&#xff08;样例文件&#xff09; 题目背景 质数&#xff08;又称“素数”&#xff09;是指在大于 11 的自然数中&#xff0c;除了 11 和它本身以外不再有其他因数的自然数。 题…...

mac笔记本如何快捷键截图后自动复制到粘贴板

前提&#xff1a;之前只会进行部分区域截图操作&#xff08;commandshift4&#xff09;操作&#xff0c;截图后发现未自动保存在剪贴板&#xff0c;还要进行一步手动复制到剪贴板的操作。 mac笔记本如何快捷键截图后自动复制到粘贴板 截取 Mac 屏幕的一部分并将其自动复制到剪…...

高考加油!UI界面生成器!

这个高考助力标语生成器具有以下特点&#xff1a; 视觉设计&#xff1a;采用了蓝色为主色调&#xff0c;搭配渐变背景和圆形装饰元素&#xff0c;营造出宁静而充满希望的氛围&#xff0c;非常适合高考主题。 标语生成&#xff1a;内置了超过 100 条精心挑选的高考加油标语&a…...

window ollama部署模型

注意去官网下载ollama,这个win和linux差别不大,win下载exe,linux用官网提供的curl命令 模型下载表:deepseek-r1 使用命令:Ollama API 交互 | 菜鸟教程 示例: 1.查看已加载模型: 2.文本生成接口 curl -X POST http://localhost:11434/v1/completions -H "Conte…...

用mediamtx搭建简易rtmp,rtsp视频服务器

简述&#xff1a; 平常测试的时候搭建rtmp服务器很麻烦&#xff0c;这个mediamtx服务器&#xff0c;只要下载就能运行&#xff0c;不用安装、编译、配置等&#xff0c;简单易用、ffmpeg推流、vlc拉流 基础环境&#xff1a; vmware17&#xff0c;centos10 64位&#xff0c;wi…...

ubuntu安装devkitPro

建议开个魔法 wget https://apt.devkitpro.org/install-devkitpro-pacman chmod x ./install-devkitpro-pacman sudo ./install-devkitpro-pacman&#xff08;下面这句如果报错也没事&#xff09; sudo ln -s /proc/self/mounts /etc/mtab往~.bashrc添加 export DEVKITPRO/o…...