本篇属于 智能体面试与工程基础 系列。
数据库负责持久化,运行时决定并发与资源管理的边界。理解这些基础,才能解释 Agent 为什么会丢状态、重复执行或越跑越慢。
B01|数据库三大范式分别约束什么?
可背版: 第一范式要求属性值符合关系模型下的原子性;第二范式在第一范式基础上,要求非主属性对每个候选键完全函数依赖,消除对候选键真子集的部分依赖;第三范式进一步约束传递依赖。正式定义中,对每个非平凡函数依赖 X→A,X 是超键或 A 是主属性。
例子: 工具调用表的键是 (run_id, call_id),但用户名称只依赖 run 所属用户,把它重复塞进每条调用会增加更新异常。是否适度反规范化,要由查询需求和一致性维护方案决定,不能说“有外键就是第三范式”。
B02|InnoDB 有哪些关键机制?
可背版: InnoDB 支持事务、外键、MVCC 和崩溃恢复,常用于事务业务。锁包括记录锁、间隙锁、next-key锁及表级意向锁等,取决于访问路径、语句和隔离级别。InnoDB 使用聚簇索引组织表数据。不要背 MyISAM 总是读得快、InnoDB 全文索引不完善这类脱离版本的结论。
B03|DELETE、TRUNCATE 和 DROP 有什么区别?
可背版(MySQL/InnoDB): DELETE 删除满足条件的行,事务内未提交的删除可以回滚,并可触发 DELETE 触发器;TRUNCATE 清空表,属于 DDL,通常隐式提交、重置自增计数,不触发 DELETE 触发器;DROP 删除表定义和数据。不能把某个数据库的页锁、日志规则推广到所有数据库。MySQL TRUNCATE 文档
B04|事务隔离级别与读写行为如何理解?
可背版: InnoDB 默认 REPEATABLE READ。普通一致性读使用快照;锁定读和写入的行为不同,要结合索引和锁范围讨论。不要只背“可重复读解决/不解决幻读”一句话。现代 MySQL 可用 SELECT @@transaction_isolation; 查询隔离级别。官方隔离说明
Agent追问: 两个请求同时更新同一会话,没发生脏读也可能丢更新。需要版本条件更新、行锁或按会话串行化;隔离级别不自动实现业务幂等。
深入一层:MVCC。 InnoDB 用行版本上的事务信息与 undo 记录支持历史版本访问,一致性读依据读视图判断版本可见性。RR 下普通一致性读通常复用首次一致性读建立的快照,RC 下每次一致性读建立新快照;本事务自身的修改还具有特殊可见性,不能简化成“所有语句永远看同一个旧数据库”。多版本机制 · 一致性读
幻读要分场景: 普通快照查询与 SELECT ... FOR UPDATE、UPDATE 等锁定访问不同。RR 下索引范围扫描可用 next-key lock(记录与前方间隙)阻止其他事务向受保护范围插入;唯一索引等值命中等情况锁范围又不同。混用快照读和锁定访问,不能承诺看见相同集合。面试时应给出隔离级别、索引、SQL 和事务交错顺序再分析。幻行与 next-key locking
B05|事务持久性与跨系统操作有什么边界?
ACID 的持久性描述提交后的保证;实际故障下的丢失风险还取决于刷盘配置、存储可靠性与复制方案。数据库事务也不能自动回滚已发出的邮件或第三方付款。跨系统操作需要状态记录、幂等与补偿设计。
B06|如何选择全局 ID,并处理时钟回拨?
可以监控并拒绝超过阈值的回拨、等待时钟追上或采用逻辑时间方案,同时保证节点ID分配不冲突。具体方案要权衡可用性与唯一性;“多加一位”不是完整设计。UUID 也并非所有版本都无序,不能一概而论。
B07|如何阅读 SQL 执行计划?
先看访问方式、实际选择的索引、估算行数和额外操作,再结合真实耗时与数据分布。支持时使用 EXPLAIN ANALYZE 获取实际执行信息,但它会执行查询,应考虑影响。不能只看到用了索引就判定已经优化。
B08|UNION 与 UNION ALL 如何处理重复和顺序?
UNION 默认消除重复行,UNION ALL 保留重复行;想保证输出顺序必须显式 ORDER BY。性能差异应结合去重成本和执行计划判断。MySQL 集合操作文档
B09|IN 与 EXISTS 如何选择?
IN 表达集合成员关系,EXISTS 判断子查询是否存在行。优化器可以转换为半连接等执行策略;不能只按“大表小表”口诀选。还要注意 NULL,尤其 NOT IN 与 NOT EXISTS 的语义差异。MySQL 优化说明
B10|什么时候全表扫描是合理的?
小表或读取大部分数据时,扫描可能合理。索引按查询过滤、连接和排序设计,不是单纯“区分度最高的放最左”;覆盖索引、减少列、分区也不会自动消除扫描。优化应以延迟、资源和写入成本为目标。
B11|JVM 运行时数据区与 Java 内存模型有什么区别?
堆、栈、方法区等属于 JVM 运行时数据区;Java Memory Model 讨论并发读写可见性、顺序和 happens-before 等规则。线程私有的栈不意味着栈里引用的对象也私有;堆里对象也可以通过不可变设计或同步实现安全访问。JVM规范 · JMM规范
B12|引用类型、垃圾回收与类初始化如何区分?
强、软、弱、虚引用描述引用语义;可达性、引用处理和垃圾收集器算法是相关但不同的概念。类加载与初始化也要分开;父类初始化不会自动初始化所有子类。具体初始化触发条件、静态常量与接口方法规则需要结合目标 JDK 版本理解。
B13|高并发问题应该从哪里入手?
先测瓶颈是模型限额、I/O、CPU、数据库还是队列积压,再决定缓存、限流、异步、扩容。加锁解决一致性,不直接提高吞吐;分库分表也不是默认第一步。
B14|进程、线程与并发安全是什么关系?
进程通常拥有独立虚拟地址空间,线程共享进程资源,但进程仍可竞争数据库、文件或共享内存。线程未捕获异常不必然使整个进程崩溃,应区分语言运行时与原生层故障。安全性取决于共享资源及同步,而不是进程数量。
B15|内存泄漏与内存溢出有什么区别?
不再需要的对象仍被全局容器、缓存或回调引用,GC就可能无法回收。内存溢出也可能由合理但过大的活跃数据造成,不一定有泄漏。Agent常见风险是无界对话、完整工具输出、未释放的连接和待执行任务积压。
B16|Java 专项:线程池参数与十个任务如何推演?
口述: corePoolSize 决定优先创建的核心线程数;核心数达到后优先尝试入队;队列满了才继续扩至 maximumPoolSize;仍不能接收则执行拒绝策略。keepAliveTime 默认作用于超过核心数的空闲线程,可显式允许核心线程超时。无界队列通常使最大线程数难以发挥扩容作用。JDK 21 ThreadPoolExecutor
给定条件再推演: 假设 core=2、max=4、有界队列容量=3、AbortPolicy,池处于运行状态,提交期间已启动任务全部阻塞、不会完成,并且调用方捕获拒绝异常后继续提交。
| 提交任务 | 接收结果 |
|---|---|
| 1–2 | 各创建一个核心工作线程 |
| 3–5 | 进入等待队列 |
| 6–7 | 队列已满,各创建一个额外工作线程 |
| 8–10 | 达到最大线程数且队列仍满,被拒绝 |
这是接收路径,不是完成顺序;任务6可能比队列里的任务3先开始。去掉“任务持续阻塞”的假设后,结果随调度和完成速度变化。
追问: CallerRunsPolicy 为什么会拖慢请求线程?无界队列为什么可能变成内存问题?回答时联系请求deadline、拒绝后的用户反馈与任务是否允许丢弃。
B17|P0:Redis 数据结构、分布式锁和租约过期如何处理?
| 数据结构 | Agent 服务中的可选用途 |
|---|---|
| String | 带 TTL 的缓存、计数、租约值 |
| Hash | 会话摘要或任务字段;多字段业务更新仍需原子性设计 |
| List | 简单待办队列;可靠消费语义需要另行设计 |
| Set | 成员去重、集合关系 |
| Sorted Set | 排序、时间窗口或到期任务索引 |
| Stream | 事件与消费者组;确认、积压和重投仍需管理 |
这些是使用示例,并不要求项目把所有状态都放 Redis。Redis 数据类型
锁的基本口述: 单实例租约可用 SET key random_token NX PX ttl 原子获取;释放时通过原子脚本检查 token 相同再删除,不能无条件 DEL。续租也要校验所有权。进程暂停、网络延迟或续租失败会让租约失效,而原任务可能仍在运行;watchdog 不能让这一风险消失。Redis 分布式锁
业务上的关键: 假设 A 锁过期,B 获得新锁并写入,A 恢复后仍可能覆盖 B。需要下游支持单调递增 fencing token 并拒绝旧持有者,或采用数据库版本条件更新等机制。随机所有权 token 不是 fencing token。无法校验租约时代的外部 API,应依赖业务幂等、对账与补偿,不能仅靠锁保证“恰好一次”。
追问: Redis 主节点故障切换、GC 暂停超过租约时会怎样?先明确锁只是减少重复工作,还是承担不能违反的业务一致性保证。