Spring Data JPA 与 MySQL 集成

共 19 题
📑 题目列表 19 题
#
★★★

1. InnoDB 自适应哈希索引(AHI)在 Spring Boot 4.0 高并发点查场景下是否反而导致缓冲池污染

InnoDB 的自适应哈希索引(Adaptive Hash Index,AHI)在 Spring Boot 4.0 高并发点查(primary key 或唯一索引等值查询)场景下,是否可能反而导致缓冲池污染,从而损害性能?

  • 自适应哈希索引的构建原理与触发条件
  • 缓冲池污染(buffer pool pollution)的概念与诱因
  • AHI 在并发点查场景下的收益与代价权衡

自适应哈希索引是 InnoDB 在内存中为热点 B+ 树索引页自动构建的哈希索引,它由 InnoDB 依据"访问频率"自主决定哪些页值得映射,无需 DBA 干预。在纯粹的高并发等值点查场景下,AHI 通常能显著降低 B+ 树逐层查找的 CPU 开销,是有益的。但"缓冲池污染"问题确实存在,原因在于:AHI 表的构建与维护本身占用 buffer pool 内存;当访问模式突变(例如瞬时全表扫描、批量扫描或非热点查询),大量冷数据页被加载进 buffer pool,同时哈希表会把维护成本集中在冷页上,形成"热点之外的页占据内存"的污染,反而挤占真正热点页的缓存空间。此外,AHI 的哈希表是全局锁保护的(本版本仍有锁竞争),在超高并发点查下锁争用可能成为瓶颈。因此不能说 AHI 一定会导致污染,但需要关注内存占用、访问模式突变与锁竞争三方面风险。

该问题考察的是"自动优化机制在真实负载下是否有副作用"的辩证思维。答案应区分"稳态点查"与"访问模式突变"两种场景:稳态下 AHI 是正收益,突变负载下才可能出现污染。回答时点出 AHI 的特点(自适应、内存占用、全局哈希锁)以及污染的具体机制,比单纯回答"是"或"否"更有深度。

#
★★★

2. Spring Boot 4.0 中 JPA 派生查询方法命名自动生成索引与 DBA 人工审计的协作流程

在 Spring Boot 4.0 中,JPA 的派生查询方法(findByXxxAndYyy)会根据方法名自动生成 SQL,但不会自动生成索引。请说明如何建立"JPA 派生查询方法命名"与"DBA 人工审计索引"的协作流程,以及为什么要这样做?

  • JPA 派生查询方法命名规则与 SQL 生成机制
  • 索引由谁创建、何时审计
  • 派生查询与慢 SQL 的关联追溯

JPA 派生查询方法(如 findByUsernameAndStatus)只是把方法名解析为 WHERE 条件,Spring Data 并不会为这些条件自动创建索引,索引仍需 DBA 或 Flyway 迁移脚本创建。协作流程的核心是"可追溯性":应在实体 Repository 方法上通过注释或命名规范标注预期查询条件,并在代码评审中导出"全部派生查询方法 → 对应 SQL → 所需索引"的清单;DBA 依据该清单与慢查询日志(slow_query_log)比对,确认索引是否命中(EXPLAIN、索引覆盖)。实践中可借助 Hibernate 的 show-sql 或 SQL 日志把派生方法生成的 SQL 记录下来,再配合监控平台(如连接池慢 SQL、MySQL slow log)把"高消耗 SQL"反查回具体的 Repository 方法,从而驱动索引的创建与优化。真正的自动索引生成(如 Hibernate 的 hibernate.hbm2ddl.auto=update 或 @Index)在生产环境通常被禁用,改由 Flyway 迁移脚本管理,保证可审计、可回滚。

本题的关键是厘清"派生查询自动生成 SQL"与"索引的人工管理"两个层面。考察点在于:方法命名只是 SQL 层,索引仍需人工/迁移管理;审计流程要能建立"方法名 → SQL → 索引 → 慢查询"的闭环,才能避免索引缺失导致的全表扫描。

public interface UserRepository extends JpaRepository<User, Long> {
    // 派生查询按 username + status 过滤,需对应索引 (username, status)
    List<User> findByUsernameAndStatus(String username, Integer status);
}
-- 由 Flyway 迁移脚本创建,而非交给 JPA 自动生成
CREATE INDEX idx_user_username_status ON user (username, status);
#
★★★

3. Spring Cloud 微服务链路中数据库索引变更的灰度发布策略与慢 SQL 监控触发条件

在 Spring Cloud 微服务架构中,数据库索引变更(新增、删除、重建索引)如何设计灰度发布策略?慢 SQL 监控的触发条件应如何设定?

  • 数据库变更与代码发布的协调(变更顺序)
  • 索引变更的灰度与回滚策略
  • 慢 SQL 监控指标与阈值

索引变更属于数据库 schema 变更,与代码发布必须解耦并遵循"前后兼容"原则。灰度发布策略通常包括:先在小流量或影子库上执行 EXPLAIN 验证新索引是否被选中、是否命中预期执行计划;再在低峰期分阶段对部分库执行 ONLINE DDL(如 MySQL 8.0 的 ALGORITHM=INPLACE, LOCK=NONE),避免锁表;同时保留旧索引直至新索引稳定(如需删除旧索引,须先发新索引、观察无报错后再删除)。慢 SQL 监控的触发条件一般从"执行时间"与"扫描行数"两个维度设定:例如执行时间超过阈值(如 500ms/1s)或扫描行数超过某数量级(如 rows 超过 10 万)即告警,并联合 EXPLAIN 判断是否缺索引;监控可结合网关/链路追踪(Sleuth/Trace)把慢 SQL 关联到具体微服务与方法。回滚方面,索引变更可通过"再建一个索引"或"保留旧索引"的方式快速回退,比代码回滚更可控。

本题考察"数据库变更管理"与"可观测性"的工程实践。关键词是"先加后删、灰度验证、可回滚"以及"慢 SQL 的触发条件(时间+扫描行数)"。回答应体现变更与发布解耦、破坏性变更的规避。

#
★★★

4. Spring Data JPA 在 OPTIMISTIC 与 PESSIMISTIC_WRITE 锁模式下生成 SQL 的差异及索引依赖

Spring Data JPA 在 OPTIMISTIC(乐观锁)与 PESSIMISTIC_WRITE(悲观写锁)两种锁模式下生成的 SQL 有何差异?它们各自对索引的依赖是什么?

  • 乐观锁与悲观锁的 SQL 形态差异
  • @Version 字段与 WHERE 条件
  • 悲观锁对索引依赖(避免全表扫描加锁)

乐观锁场景下,实体需声明 @Version 字段(如整数 version 或 timestamp),UPDATE 语句会生成 UPDATE ... SET version=version+1 WHERE id=? AND version=?,通过受影响行数(0)判断并发冲突并抛出 OptimisticLockException;它不依赖特殊索引,但最好以主键/唯一索引定位行。悲观锁则通过 @Lock(LockModeType.PESSIMISTIC_WRITE) 在查询时生成 SELECT ... FOR UPDATE,直接锁定被选中的行直到事务结束。悲观锁对索引的依赖更关键:WHERE 条件若命中索引,则只锁定位到的索引行;若未命中索引导致全表扫描,InnoDB 会在二级索引与主键上锁大量行甚至引发间隙锁,扩大锁范围、降低并发度。因此悲观锁查询必须保证 WHERE 字段有合适的索引,并配合隔离级别避免不必要的锁。

核心差异是"乐观锁在 UPDATE 时用 version 校验冲突、悲观锁在 SELECT 时用 FOR UPDATE 预占锁"。索引依赖方面,乐观锁依赖定位行的准确性,悲观锁依赖索引以缩小锁范围(避免全表锁与间隙锁放大)。

@Entity
public class Account {
    @Id private Long id;
    @Version private long version; // 乐观锁
    private BigDecimal balance;
}
// 悲观锁
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Account a where a.id = :id")
Account findForUpdate(@Param("id") Long id);
#
★★★

5. Spring Boot 4.0 与 MySQL 8.4 集成时,间隙锁(Gap Lock)对并发插入的影响与隔离级别选择

Spring Boot 4.0 与 MySQL 8.4 集成时,间隙锁(Gap Lock)对并发插入有何影响?应如何选择隔离级别?

  • 间隙锁在 REPEATABLE READ 下的行为
  • 间隙锁对并发插入的阻塞
  • 隔离级别选择(RR vs RC)与幻读

在 MySQL InnoDB 默认的 REPEATABLE READ(RR)隔离级别下,范围查询或等值命不中索引时会产生间隙锁(Gap Lock),其作用是防止幻读(阻止其他事务在间隙内插入记录)。间隙锁会阻塞并发插入:例如事务 A 在 (10,20) 区间加了间隙锁,事务 B 尝试插入 id=15 会被阻塞直到 A 提交。这在 Spring Boot 高并发场景下可能显著降低吞吐。若业务对幻读容忍度较高,可改用 READ COMMITTED(RC)隔离级别——RC 下 InnoDB 只使用记录锁(Record Lock)和临键锁,不使用间隙锁,从而减少锁冲突、提升并发插入能力;代价是需要用其他手段(如 SELECT FOR UPDATE 或应用层约束)防止幻读。通过 spring.datasource.hikari.connection-init-sqlSET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED 可设置 Spring 层面的隔离级别。需要权衡"数据一致性"与"并发吞吐"。

间隙锁是 RR 隔离级别下防幻读的机制,代价是阻塞并发插入。选择 RC 可消除间隙锁、提升并发,但需接受或另行处理幻读。回答应说明"为什么要选 RC"以及"如何配置"。

#
★★★

6. MySQL 8.0 引入的 SKIP LOCKED 与 NOWAIT 在 Spring Data JPA 中的语义映射与代码示例

MySQL 8.0 引入的 FOR UPDATE SKIP LOCKEDFOR UPDATE NOWAIT 在 Spring Data JPA 中如何映射?请给出代码示例?

  • SKIP LOCKED 与 NOWAIT 的语义
  • JPA 中利用 PESSIMISTIC_WRITE 与 hint 的实现
  • 任务队列/并发控制的典型应用

FOR UPDATE SKIP LOCKED 表示跳过当前已被其他事务锁定的行,返回未锁定的行,适合任务队列消费、批次领取等场景(多实例并发抢取互不冲突);FOR UPDATE NOWAIT 表示若目标行已被锁则立即报错而非等待,适合对超时敏感的 UI 场景。Spring Data JPA 中,@Lock(LockModeType.PESSIMISTIC_WRITE) 会生成 FOR UPDATE,但默认语义是"等待"。要得到 SKIP LOCKED / NOWAIT 语义,需通过 Hibernate 的查询 hint 实现:@QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "-2"))-2 对应 SKIP LOCKED,0 对应 NOWAIT(实际映射依赖 Hibernate 方言对 MySQL 的支持)。Hibernate 6.x 已支持 jakarta.persistence.lock.timeout-2(SKIP LOCKED)与 0(NOWAIT)映射到 MySQL 的 FOR UPDATE SKIP LOCKED / FOR UPDATE NOWAIT

关键词是"语义映射":JPA 的 LockModeType.PESSIMISTIC_WRITE 只生成 FOR UPDATE,SKIP LOCKED/NOWAIT 需通过 lock.timeout hint 的特定值(-2/0)触发,且依赖 Hibernate 方言支持。这是 JPA 与 MySQL 新特性结合的典型考点。

@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(@QueryHint(name = "jakarta.persistence.lock.timeout", value = "-2")) // SKIP LOCKED
@Query("select j from Job j where j.status = :status order by j.id")
List<Job> fetchJobs(@Param("status") String status);
#
★★★

7. MySQL Group Replication 在多主模式下写冲突检测与 Spring Boot 事务回滚机制的兼容性

MySQL Group Replication(MGR)在多主(Multi-Primary)模式下如何检测写冲突?它与 Spring Boot 的事务回滚机制是否兼容?

  • MGR 多主模式的写冲突检测机制(certification)
  • 冲突检测失败时的错误语义
  • 与 Spring 事务回滚的配合

MySQL Group Replication 多主模式允许所有节点同时接受写,其写冲突检测基于"certification(认证)机制":每个写事务在提交时,通过共识协议(Paxos)在组内广播并检查是否与其他并发事务的读写集合冲突,若冲突则判定该事务必须回滚(事务被终止,返回错误)。这与 Spring 事务回滚机制是兼容的:Spring 的 @Transactional 依靠 JDBC 抛出的异常(如 SQLException/DeadlockException)触发回滚,而 MGR 冲突检测失败时驱动会抛出冲突错误,Spring 捕获后调用 rollback 回滚当前事务。因此,只要 application 正确处理了冲突异常(如重试、幂等补偿),业务侧无需特殊改动。需要注意:MGR 冲突检测发生在"提交阶段",若用二进制日志(binlog)对比,被判定冲突的事务会回滚,但应用可能已执行了部分操作,因此要求业务具备幂等性并由应用层重试。

核心是"MGR 提交时的认证冲突检测"与"Spring 基于异常的回滚"如何衔接。两者天然兼容——冲突异常经 JDBC 抛出、Spring 捕获并回滚。答题要点出冲突检测的时机(提交阶段)与业务幂等重试的要求。

#
★★★

8. 使用 SELECT ... FOR UPDATE SKIP LOCKED 实现任务队列与 Redisson 分布式锁的工程取舍

在 Spring Boot 中,使用 SELECT ... FOR UPDATE SKIP LOCKED 实现任务队列与使用 Redisson 分布式锁相比,两者在工程上如何取舍?

  • SKIP LOCKED 任务队列的机制与适用场景
  • Redisson 分布式锁的机制与适用场景
  • 两者的性能、复杂度与一致性差异

SELECT ... FOR UPDATE SKIP LOCKED 实现任务队列的思路是:在数据库表中用状态字段标记任务,多个消费者通过 SELECT ... FOR UPDATE SKIP LOCKED 原子地领取未被锁定的任务行,领取后更新状态,天然避免重复消费。其优点是简单、与业务数据同库一致、无需额外中间件,适合中小规模、任务量可控的场景;缺点是依赖数据库为任务加锁、高频轮询会带来数据库压力,且不适用于跨库/跨服务的全局任务。Redisson 分布式锁基于 Redis,通过 tryLock 获取互斥锁,适合分布式、高并发、需要超时/可重入/看门狗续期的场景,锁的获取更轻量、吞吐更高;缺点是引入 Redis 依赖,若 Redis 不可用或锁超时误释放需小心处理,且锁与业务数据分离(Redis 锁住了,但数据库状态仍需同步)。工程取舍:任务数据量小、要求强一致、复用同一数据库实例时选 SKIP LOCKED;任务量大、跨节点、追求高吞吐与低延迟时选 Redisson 分布式锁。

本题是"数据库实现"与"分布式中间件实现"的对比题。答题要点出机制差异(SKIP LOCKED 靠数据库行锁,Redisson 靠 Redis 锁),以及各自的适用场景、优缺点与一致性问题。

#
★★

9. MySQL 8.0 引入的 LOCK TABLES 与事务混合使用时的隐式提交点及 Spring 事务管理器如何感知

MySQL 8.0 中,LOCK TABLES 与事务混合使用时存在哪些隐式提交点?Spring 事务管理器如何感知这些隐式提交?

  • LOCK TABLES 与事务的隐式提交行为
  • 隐式提交对事务边界的影响
  • Spring 事务管理器对连接状态的感知

MySQL 中,LOCK TABLESUNLOCK TABLESSET TRANSACTION、DDL(CREATE/ALTER/DROP)等语句会触发隐式提交(implicit commit)——即执行这些语句前会先提交当前事务。这意味着在同一事务中混用 LOCK TABLES 与显式事务(BEGIN/COMMIT)时,事务的边界会被打破,之前开启的事务在 LOCK TABLES 时已被提交,之后的 DML 归属新的隐式事务。针对 Spring,Spring 事务管理器(DataSourceTransactionManager)运行在 JDBC Connection 之上,通过 connection.setAutoCommit(false) 开启事务、commit()/rollback() 结束事务,它并不感知 MySQL 内部触发的隐式提交点——它只看到连接状态。因此,一旦应用在事务内执行了 LOCK TABLES 等隐式提交语句,Spring 认为事务仍"开启",但数据库实际已提交,导致后续回滚可能无法覆盖已提交的部分,破坏事务语义。工程上应避免在 Spring 管理的 @Transactional 事务内混用 LOCK TABLES,改用 InnoDB 的 SELECT ... FOR UPDATE 或行锁。

本题考察"隐式提交"这一 MySQL 陷阱与 Spring 事务管理器的边界。Spring 只管理 JDBC 连接层,无法感知数据库隐式提交,因此应用层必须避免在事务内使用会隐式提交的语句。

#
★★

10. MySQL 连接串与 JDBC 驱动参数(useSSL、characterEncoding、serverTimezone、rewriteBatchedStatements)对 JPA 应用的正确性与性能影响

MySQL JDBC 连接串中的 useSSL、characterEncoding、serverTimezone、rewriteBatchedStatements 等参数对 Spring Data JPA 应用的正确性与性能有何影响?

  • 各连接参数的正确性影响
  • 时区与编码问题
  • rewriteBatchedStatements 对批量性能的影响

useSSL 决定是否启用 SSL 加密连接,生产环境应开启(useSSL=true 并配置证书)以保护敏感数据,但会带来少量性能开销;测试环境可关闭。characterEncoding 用于设定字符集(如 UTF-8),若与数据库、表的字符集不一致会导致中文乱码,应统一为 utf8mb4。serverTimezone 用于指定服务器时区,避免 JDBC 与数据库时区不一致导致的日期时间偏移(尤其在 MySQL 8.x 驱动对时区校验更严格时,必须显式配置,否则可能报错)。rewriteBatchedStatements=true 会重写批量 addBatch 语句,将多条单条 INSERT 合并为一条多 VALUES 的 INSERT,显著减少网络往返与服务器解析开销,大幅提升批量插入性能;若为 false,则每条语句单独执行,批量性能差。综合来看,连接串参数既有正确性层面的(编码、时区、SSL),也有性能层面的(rewriteBatchedStatements),需结合环境合理配置。

本题考察"连接串参数的多维影响"。正确性(编码/时区/SSL)与性能(批量重写)要分开讨论,并给出具体配置建议,是工程实践中非常常见的坑。

jdbc:mysql://localhost:3306/db?useSSL=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
#
★★

11. 唯一索引与业务唯一约束在并发插入下的间隙锁竞争对 Spring Boot 事务吞吐的影响

在并发插入场景下,唯一索引与业务唯一约束如何产生间隙锁竞争?对 Spring Boot 事务吞吐有何影响?

  • 唯一索引插入时的锁行为(duplicate key check)
  • 并发插入同一唯一值时的间隙锁竞争
  • 对事务吞吐的影响与缓解

并发插入到唯一索引时,InnoDB 会先做唯一性检查:若插入的键值在范围内不存在,会在插入位置加间隙锁/插入意向锁以保护唯一性;若多个事务同时插入同一个唯一键值,它们会在间隙上相互等待,形成锁竞争,甚至出现死锁(一个事务回滚后其他事务才继续)。这种"唯一约束下的间隙锁竞争"在 Spring Boot 高并发场景下会显著降低事务吞吐,具体表现为大量超时、锁等待与死锁重试。缓解手段包括:使用 INSERT ... ON DUPLICATE KEY UPDATE 或 INSERT IGNORE 将"检查+插入"合并为原子操作,减少竞争窗口;提前分配唯一键(如基于业务号段)避免并发撞同一值;适当降低事务隔离级别;以及用分布式锁/幂等键在应用层先做唯一性仲裁。回答时需指出唯一索引的正确性由数据库保证,但并发撞唯一键会引入锁竞争,需要业务侧配合。

本题考察"索引正确性"与"并发锁竞争"的辩证关系。唯一索引保证唯一性,但并发插入同一键值会触发间隙锁竞争甚至死锁,需通过原子 UPSERT、幂等键或减少冲突来缓解。

#
★★

12. 在 GraalVM Native Image 下启动时执行 schema 校验与索引一致性检查的可行性

在 GraalVM Native Image 下,Spring Boot 应用启动时执行 schema 校验与索引一致性检查是否可行?有哪些限制?

  • GraalVM Native Image 的编译期特性
  • schema 校验在原生镜像下的时机与限制
  • 反射与动态代理的兼容性

GraalVM Native Image 将应用编译为原生可执行文件,采用"编译期静态分析 + 运行时无 JIT"的 AOT 方式。schema 校验(Hibernate 的 hbm2ddl 校验)与索引一致性检查本质上是在启动时连接数据库执行 SQL 查询(如 information_schema),这在原生镜像下是可行的,因为这些运行时数据库交互并不依赖 JVM 动态特性。但存在以下限制:一是 Hibernate 的元数据映射、反射、动态代理等特性,在原生镜像下需要显式配置(reflect-config.json、proxy-config.json)声明,否则启动时可能因反射缺失而失败;二是启动时建立连接会拉长启动时间,且若数据库不可用会导致启动失败,需要在 deployment 中处理;三是原生镜像对 XML 解析、注解扫描等静态资源类加载有额外要求。因此可行性是"可以但需配置",需在构建期把 Hibernate 需要的反射/代理信息纳入 reachability metadata,并平衡启动校验与启动速度。

本题考察 GraalVM 原生镜像与 ORM 特性(反射、动态代理)的兼容性。schema 校验本身可行,但 Hibernate 的反射/代理需在构建期配置,且要注意启动时间与失败处理。

#
★★

13. 在 Spring Data JPA 中使用 @Index 注解与 Flyway 自动创建索引的兼容性与事务回滚差异

在 Spring Data JPA 中使用 @Index 注解与 Flyway 自动创建索引,两者在兼容性上有何区别?事务回滚方面有何差异?

  • @Index 注解与 hbm2ddl 的关系
  • Flyway 迁移脚本管理索引
  • 事务回滚(DDL 隐式提交)的差异

@Index 注解用于声明实体类上的索引,但只有在 Hibernate 的 hbm2ddl.auto 设为 update/create 等由 JPA 自动管理 schema 时才会生效;生产环境通常 hbm2ddl.auto=none,此时 @Index 仅作为文档,不实际建索引。Flyway 通过迁移脚本(V1__xxx.sql)显式创建索引,由版本管理控制,可审计、可回滚、可灰度。兼容性上,两者不应混用:若同时开了 hbm2ddl.update 又用 Flyway,可能出现索引重复创建或冲突,最佳实践是统一用 Flyway 管理 schema,hbm2ddl.auto 设为 none。事务回滚差异:MySQL 的 DDL(CREATE INDEX)会触发隐式提交,因此无法在事务中回滚——Flyway 迁移在 MySQL 上执行 DDL 时,若中途失败,已执行的 DDL 无法回滚,只能靠前向修复(新增迁移脚本);而 @Index 由 hbm2ddl 生成时同样受隐式提交限制。因此索引变更的"回滚"本质是"新增一个反向 DDL 或保留旧索引",而非事务回滚。

本题考察"JPA 注解建索引"与"Flyway 迁移建索引"的工程取舍。核心是生产环境统一用 Flyway、hbm2ddl=none,以及 DDL 隐式提交导致无法事务回滚的本质。

#
★★

14. 使用 SEQUENCE 引擎与 AUTO_INCREMENT 在 Spring Cloud 微服务分库场景下的全局唯一性取舍

在 Spring Cloud 微服务分库场景下,使用 SEQUENCE 与 AUTO_INCREMENT 生成主键,在全局唯一性上如何取舍?

  • AUTO_INCREMENT 的单库局限
  • SEQUENCE 的实现与跨库方案
  • 全局唯一 ID 策略

AUTO_INCREMENT 依赖单库的自动递增计数器,只能在单个数据库实例内保证唯一,一旦分库分表,各库的 AUTO_INCREMENT 会各自从 1 开始,产生重复主键,无法保证全局唯一性。SEQUENCE 在 MySQL 中并非原生支持(MySQL 8.0 无标准 SEQUENCE,需借助其他机制),但可在应用层实现"号段模式"(每次从数据库取一段连续号,如 1000 个,由应用分发),从而在分库场景下保证全局唯一且趋势递增。在 Spring Cloud 微服务分库场景下的取舍:若仍用 AUTO_INCREMENT,需通过调整起始值(如每个分片设置不同步长或偏移)或改造为全局 ID 方案;更推荐使用雪花算法(Snowflake)、号段模式(LEAF)或分布式 ID 中心,它们在全局唯一性、排序性、性能之间取得平衡。取舍原则:小规模单库可用 AUTO_INCREMENT;分库分表必须切换到全局唯一 ID 方案,号段模式兼顾性能与有序性,雪花算法适合高并发分布式。

本题考察分库分表下主键的唯一性。AUTO_INCREMENT 仅单库唯一,分库需改全局 ID 方案;号段模式与雪花算法是主流取舍。

#
★★

15. MySQL 8.4 中 read_only 与 super_read_only 在 Spring Boot 多租户数据隔离中的权限模型

MySQL 8.4 中 read_only 与 super_read_only 参数在 Spring Boot 多租户数据隔离中如何发挥作用?权限模型如何设计?

  • read_only 与 super_read_only 的区别
  • 只读库/从库的写保护
  • 多租户隔离的账号与权限设计

read_only=ON 时,普通用户无法执行写操作(INSERT/UPDATE/DELETE/DDL),但具有 SUPER 权限的用户仍可读写;super_read_only=ON 则连 SUPER 用户也禁止写,用于彻底保护只读节点(如从库、备用库)。在 Spring Boot 多租户数据隔离中,可利用这两个参数强制只读库的写保护:例如租户的只读报表数据源、从库数据源,可用 super_read_only 防止误写。权限模型上,多租户隔离通常为每个租户或每类租户创建独立数据库账号/数据库,配合数据库级 GRANT 精确控制读写权限;同时可用 read_only 约束共享/只读库,避免应用层误写破坏租户数据。设计要点:读写分离库设 read_only/super_read_only,应用对只读数据源只配只读账号;多租户通过 schema 隔离 + 账号级权限 + 参数级写保护三层叠加,实现数据隔离与防误写。

本题考察 MySQL 权限参数与多租户隔离的结合。read_only 与 super_read_only 都用于只读保护,区别在于是否对 SUPER 生效;多租户靠账号权限 + 参数保护叠加。

#
★★

16. rewriteBatchedStatements=true 与 Hibernate 批量插入(hibernate.jdbc.batch_size)如何配合提升批量写入吞吐

rewriteBatchedStatements=true 与 Hibernate 的批量插入(hibernate.jdbc.batch_size)如何配合才能提升批量写入吞吐?

  • Hibernate 批量插入的 batch_size 机制
  • rewriteBatchedStatements 的 SQL 重写
  • 两者配合与注意事项

rewriteBatchedStatements=true 让 JDBC 驱动把多条 addBatch 的 INSERT 重写为一条多 VALUES 的 INSERT,从而减少网络往返与服务器解析开销。Hibernate 的 hibernate.jdbc.batch_size 控制 Hibernate 在 flush 时把多少条 SQL 拿到 JDBC 批量执行(PreparedStatement.addBatch),每达到 batch_size 就 executeBatch 一次。两者配合能显著提升吞吐:batch_size 决定"攒多少条一起提交给驱动",rewriteBatchedStatements 决定"驱动是否把多条合并成一条多 VALUES"。

本题考察"应用层批量"与"驱动层批量"的协同。batch_size 控制攒批阈值,rewriteBatchedStatements 控制驱动合并,order_inserts 可提升合并率,三者配合才能最大化批量吞吐。

spring.jpa.properties.hibernate.jdbc.batch_size: 100
spring.jpa.properties.hibernate.order_inserts: true
spring.jpa.properties.hibernate.order_updates: true
# 连接串加 rewriteBatchedStatements=true
#
★★

17. Hibernate MySQL 方言对 java.time、BigDecimal 与 JSON 类型的映射差异与精度陷阱

Hibernate 的 MySQL 方言在映射 java.time 类型、BigDecimal 与 JSON 类型时存在哪些差异与精度陷阱?

  • java.time 与 MySQL 时间类型的映射
  • BigDecimal 与 DECIMAL 的精度
  • JSON 类型映射与方言支持

java.time 类型映射:LocalDate→DATE、LocalTime→TIME、LocalDateTime→DATETIME/TIMESTAMP、Instant→TIMESTAMP,Hibernate 6 使用 JDBC 标准类型,MySQL 方言能正确映射;陷阱在于 TIMESTAMP 的时区处理、DATETIME 无时区导致偏移、以及 MySQL 的 TIMESTAMP 溢出范围(2038 问题)。BigDecimal 映射为 DECIMAL,陷阱在于默认精度与标度:若数据库列 DECIMAL 的精度/标度不足,插入时可能被 MySQL 四舍五入或报数据溢出,需用 @Column(precision, scale) 显式声明,避免精度丢失。JSON 类型:MySQL 5.7+ 支持原生 JSON,Hibernate 6 的 MySQL 方言支持将实体字段映射为 JSON,但较早版本 Hibernate 需借助自定义类型(如 hibernate-types 库的 JsonType)或字符串存储;陷阱包括 JSON 的排序/规范化、索引(需虚拟列索引)以及方言差异。综合来看,精度陷阱主要来自 DECIMAL 精度标度、时间时区、以及 JSON 类型在 Hibernate 版本间的方言支持差异。

本题考察 ORM 类型映射的边界。要分 java.time、BigDecimal、JSON 三类讨论,重点放在精度(DECIMAL 的 precision/scale)、时区(TIMESTAMP)与方言支持(JSON)三类陷阱。

#
★★

18. Spring Boot 默认开启的 spring.jpa.open-in-view(OSIV)如何把 Session 生命周期绑定到请求,视图/序列化阶段访问懒加载属性为何抛 LazyInitializationException,关闭后应选用 @EntityGraph、JOIN FETCH 还是事务内访问

Spring Boot 默认开启的 spring.jpa.open-in-view(OSIV)如何把 Session 生命周期绑定到请求?为什么视图/序列化阶段访问懒加载属性会抛 LazyInitializationException?关闭后应选用 @EntityGraph、JOIN FETCH 还是事务内访问?

  • OSIV 的机制与 Session 绑定
  • LazyInitializationException 的成因
  • 关闭 OSIV 后加载懒加载属性的三种方案

spring.jpa.open-in-view 默认开启,它通过 OpenSessionInView(OSIV)过滤器把 JPA 的 EntityManager/Session 绑定到当前请求线程,使其从请求开始到响应结束(包括视图渲染阶段)都保持打开,从而在视图/序列化阶段仍可访问懒加载属性。但若关闭 OSIV(生产推荐),Session 只在事务(@Transactional)内保持打开,事务提交后 Session 关闭,此时在视图或 JSON 序列化阶段访问懒加载属性(如未加载的集合、关联)会抛 LazyInitializationException(因为 Session 已关闭无法初始化代理)。关闭 OSIV 后的三种方案:一是用 @EntityGraph 在查询时通过 fetch 指定实体图,一次性把需要的关联加载进来;二是用 JPQL/Criteria 的 JOIN FETCH 显式 join 并 fetch 关联;三是把访问逻辑放到 @Transactional 事务方法内完成,让 Session 在事务内保持打开。三者选型:需要按需加载且结构清晰用 @EntityGraph,需要复杂过滤或控制关联用 JOIN FETCH,跨层访问且需统一事务边界用事务内访问。官方推荐关闭 OSIV,因为其长期持有数据库连接、易造成连接池耗尽与性能问题。

本题考察 OSIV 机制与懒加载的经典结合。核心是"Session 生命周期 = 事务生命周期(关闭 OSIV 后)",关闭后访问懒加载属性即抛异常;三种解决手段各有适用场景。

// 方式一:@EntityGraph 预加载关联
@EntityGraph(attributePaths = {"orders"})
@Query("select u from User u where u.id = :id")
User findWithOrders(@Param("id") Long id);

// 方式二:JOIN FETCH
@Query("select u from User u left join fetch u.orders where u.id = :id")
User findWithOrdersFetch(@Param("id") Long id);
#

19. Spring @Transactional 的 REQUIRES_NEW 与 NESTED 在 MySQL SAVEPOINT 支持下的真实语义差异

Spring @Transactional 的 REQUIRES_NEW 与 NESTED 在 MySQL SAVEPOINT 支持下的真实语义差异是什么?

  • REQUIRES_NEW 与 NESTED 的传播语义
  • MySQL SAVEPOINT 与 NESTED 的对应
  • 回滚范围与事务边界差异

REQUIRES_NEW 会挂起当前事务并开启一个全新的独立事务,新事务有自己的提交/回滚边界,与外部事务完全隔离——外部事务失败不影响内部已提交的事务,反之亦然。NESTED 则不同:它并非真正的新事务,而是利用数据库的 SAVEPOINT(保存点)在现有事务内开一个"嵌套逻辑事务",MySQL 通过 SAVEPOINT 实现。NESTED 的执行语义是:内层 savepoint 的回滚只回滚到该保存点,不影响外层事务;但内层事务的提交(Spring 的提交语义)实际上是"释放保存点",并不会独立提交,最终仍随外层事务一起提交。因此真实差异是:REQUIRES_NEW 是物理独立事务(独立提交/回滚),NESTED 是逻辑嵌套(依赖 SAVEPOINT,回滚到保存点,但不独立提交)。由于 MySQL 支持 SAVEPOINT,NESTED 在 MySQL 上可用(但要求外层也处于事务中);当外层事务回滚时,NESTED 内层也会回滚,而 REQUIRES_NEW 内层已提交不受影响。

本题考察传播语义的真实差异。REQUIRES_NEW 是"新物理事务",NESTED 是"SAVEPOINT 逻辑嵌套",关键区别在于提交时机与回滚范围(内层独立提交 vs 随外层提交)。