← 返回题目列表

MyBatis 的 Executor 有哪几种?SimpleExecutor、ReuseExecutor、BatchExecutor 有什么区别?

高频 中等 第 3 / 24 题 更新于 2026/08/03
ExecutorSimpleExecutorReuseExecutorBatchExecutor

简化版

Executor(执行器)是 MyBatis 真正执行 SQL 的核心组件,有三种:SimpleExecutor(简单执行器,默认)——每执行一条 SQL 都新建一个 Statement,用完就关,最简单但不复用;ReuseExecutor(重用执行器)——把 Statement 按 SQL 缓存起来,相同的 SQL 复用同一个 Statement(省去重复预编译的开销);BatchExecutor(批量执行器)——把多条 SQL 攒起来批量发送addBatch/executeBatch),大幅提升批量插入/更新的性能。此外还有 CachingExecutor——它是二级缓存的装饰器,包在上面三种外层,先查二级缓存、没有再走真正的 Executor。选执行器:默认 Simple、批量操作用 Batch。

详细版

三种基础 Executor

Executor行为Statement 处理适用
SimpleExecutor(默认)每条 SQL 新建 Statement,执行完关闭不复用普通增删改查
ReuseExecutor相同 SQL 复用 Statement(缓存在 Map 里)按 SQL 文本复用同一 SQL 反复执行
BatchExecutor攒批,统一 executeBatch 发送批量提交大批量插入/更新

CachingExecutor(装饰器):不是第四种独立执行器,而是二级缓存的装饰器——如果开启了二级缓存,MyBatis 会用 CachingExecutor 把上面三种之一包起来:查询先看二级缓存,命中直接返回,未命中才委托给内层真正的 Executor。

// 指定 Executor 类型
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);   // 批量执行器
// ExecutorType.SIMPLE(默认)/ REUSE / BATCH

// 全局配置默认执行器类型(mybatis-config.xml)
// <settings><setting name="defaultExecutorType" value="SIMPLE"/></settings>

BatchExecutor 的批量插入(相比逐条 insert 快很多):

SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : users) {
    mapper.insert(user);   // 不会立即执行,而是 addBatch 攒起来
}
session.flushStatements();  // 或 commit(),此时才批量 executeBatch 发送到数据库

⚠️ BatchExecutor 有个「陷阱」:insert不会立即返回受影响行数或自增主键——因为 SQL 还没真正执行(在攒批),要等 flushStatements()/commit() 才批量发送。所以「批量插入回填自增主键」在 BatchExecutor 下行为特殊,且同一批里不能混用查询(查询会强制先 flush 已攒的批,打乱批处理)。批量操作要和查询分开。

完整版教学

一、Executor 是什么:SQL 执行的引擎

MyBatis 处理一次 SQL,经过 SqlSession → Executor → StatementHandler → JDBC。其中 Executor 是真正的「执行引擎」——它负责创建 Statement、执行 SQL、处理一级缓存、管理事务提交/回滚:

SqlSession(会话门面,你直接用的)
    ↓ 委托
Executor(执行引擎,真正干活的)★
    ↓ 用
StatementHandler → JDBC PreparedStatement → 数据库

Executor 的职责:① 创建和管理 Statement② 执行增删改查③ 维护一级缓存(一级缓存就在 Executor 里,一个 SqlSession 一个 Executor 一份一级缓存);④ 事务的提交回滚。三种 Executor 的区别,主要在「怎么创建和管理 Statement」上——这直接影响性能。理解「Executor 是执行引擎、三种类型是不同的 Statement 管理策略」,就抓住了这道题的核心。

二、SimpleExecutor:最简单的「用完就扔」

SimpleExecutor默认执行器,策略最简单——每执行一条 SQL 就新建一个 Statement,执行完立即关闭

执行 SQL1:new PreparedStatement → 预编译 → 执行 → 关闭
执行 SQL2:new PreparedStatement → 预编译 → 执行 → 关闭
执行 SQL1(相同):又 new 一个 → 又预编译 → 执行 → 关闭   ← 不复用!

优点:简单、无状态、无副作用,用完即清理,适合绝大多数普通增删改查。缺点:即使执行相同的 SQL,也每次都重新创建 Statement、重新预编译(数据库要重新解析 SQL 生成执行计划)。对于「同一条 SQL 在一个会话里反复执行」的场景,这个「重复预编译」的开销就浪费了——这就是 ReuseExecutor 要优化的点。不过日常业务里同一 SQL 反复执行的情况不多,所以 Simple 作为默认够用。

三、ReuseExecutor:复用 Statement 省预编译

ReuseExecutor(重用执行器)优化了 Simple 的痛点——把 Statement 按 SQL 文本缓存起来,相同的 SQL 复用同一个 Statement

内部维护一个 Map<String, Statement>(key 是 SQL 文本)
执行 SQL1:Map 里没有 → new Statement → 放进 Map → 执行
执行 SQL1(相同):Map 里有!→ 直接复用那个 Statement → 执行(省去重新预编译)
执行 SQL2:Map 里没有 → new → 放进 Map → 执行
会话结束时,统一关闭 Map 里所有 Statement

价值:省去了「相同 SQL 重复预编译」的开销——预编译(数据库解析 SQL、生成执行计划)是有成本的,复用 Statement 就复用了已编译好的语句。适用场景:一个会话里同一条 SQL 要执行很多次(如循环里执行相同结构的查询/更新)。代价:Statement 一直缓存到会话结束才关闭,占用资源略多。日常用得不多,但「同一 SQL 高频执行」时它比 Simple 快。注意它的复用是「会话级」的——出了这个 SqlSession,缓存就清了。

四、BatchExecutor:攒批批量发送

BatchExecutor(批量执行器)针对批量写操作优化——不是一条条发 SQL,而是攒起来一批一起发

逐条执行(Simple/Reuse):
  insert1 → 发数据库 → 等结果 → insert2 → 发数据库 → 等结果 → ...
  N 次插入 = N 次网络往返 → 慢

BatchExecutor 攒批:
  insert1 → addBatch(攒着,不发)
  insert2 → addBatch(攒着)
  ...
  flushStatements/commit → executeBatch(一次性把 N 条发过去)→ 1 次网络往返 → 快

用一个数字感受:插入 1 万条数据,逐条执行是 1 万次数据库交互(每次有网络往返 + 事务开销),BatchExecutor 攒成一批(或几批)发送,交互次数骤减到几次,性能可能提升几十倍。所以大批量插入/更新必须用 BatchExecutor(或数据库的批量 SQL)。它的机制是 JDBC 的 addBatch()(攒)+ executeBatch()(批量执行)。这也是前面「批处理」那道题的底层执行器——批处理能快,靠的就是 BatchExecutor 减少数据库交互次数。

五、BatchExecutor 的陷阱:延迟执行

BatchExecutor 快,但有几个必须知道的「反直觉」行为,都源于它「攒批、延迟执行」的特性:

陷阱1:insert 不立即返回受影响行数/自增主键
  insert 时只是 addBatch,SQL 还没真正执行
  → 返回的影响行数是 -2147482646(Statement.SUCCESS_NO_INFO)之类的占位值
  → 自增主键也拿不到,要等 flush 后
  → 「批量插入回填主键」在 Batch 下要特殊处理

陷阱2:批中间不能混查询
  如果攒批过程中执行了一个查询(select)
  → MyBatis 会强制先 flushStatements(把已攒的批发出去)再查
  → 打乱了批处理的节奏,失去批量优势
  → 所以批量写和查询要分开

陷阱3:忘了 flush/commit,攒的批不会执行
  addBatch 后必须 flushStatements() 或 commit() 才真正发送

核心原因:BatchExecutor 的 SQL 是「延迟执行」的——insert 只是记账(addBatch),真正执行要等 flush。所以「立即拿结果」的假设在 Batch 下不成立。实践上:批量操作单独开一个 ExecutorType.BATCH 的 SqlSession,只做批量写、不混查询,最后统一 flush/commit

六、CachingExecutor 与选型

除了三种基础 Executor,还有一个 CachingExecutor——它不是第四种独立执行器,而是二级缓存的装饰器

开启二级缓存时,MyBatis 用 CachingExecutor 包住基础 Executor(装饰器模式):
  查询:CachingExecutor 先查二级缓存
    命中 → 直接返回(不走数据库)
    未命中 → 委托给内层的 Simple/Reuse/Batch Executor 真正查数据库 → 结果放进二级缓存

所以完整的 Executor 层次是:CachingExecutor(二级缓存装饰,可选)→ 包裹 → SimpleExecutor/ReuseExecutor/BatchExecutor(真正执行)。选型总结:

场景选择
普通增删改查(绝大多数)SimpleExecutor(默认,够用)
一个会话内同一 SQL 反复执行ReuseExecutor(复用 Statement 省预编译)
大批量插入/更新BatchExecutor(攒批,性能几十倍)
开了二级缓存自动套 CachingExecutor 装饰

一句话:默认用 Simple,批量用 Batch,Reuse 少见(特定高频同 SQL),二级缓存靠 CachingExecutor 装饰

记忆钩子:「Executor 是 SQL 执行引擎(含一级缓存):Simple 默认(每条新建 Statement 不复用)、Reuse 复用 Statement(省预编译、会话级)、Batch 攒批批量发(大批量快几十倍但延迟执行、insert 不立即返回主键、不能混查询);CachingExecutor 是二级缓存装饰器包在外层」

七、常见误区与追问

  • 误区:MyBatis 只有一种执行器。 有三种基础执行器(Simple/Reuse/Batch)+ 一个二级缓存装饰器 CachingExecutor;默认是 SimpleExecutor。
  • 误区:ReuseExecutor 的 Statement 复用是全局的。 是会话级——缓存在当前 SqlSession 的 Executor 里,会话结束就清;跨会话不复用。
  • 误区:BatchExecutor 的 insert 会立即返回自增主键。 不会——insert 只是 addBatch 攒着、SQL 还没执行,要等 flushStatements/commit 才真正发送,主键回填要特殊处理。
  • 误区:BatchExecutor 里可以随意混查询。 不行——中间执行查询会强制先 flush 已攒的批,打乱批处理;批量写和查询应分开会话/时机。
  • 追问:一级缓存和 Executor 什么关系? 一级缓存就在 Executor 里,一个 SqlSession 对应一个 Executor、一份一级缓存;所以一级缓存是会话级的。
  • 追问:CachingExecutor 是第四种执行器吗? 不是独立执行器,是二级缓存的装饰器(装饰器模式)——包在 Simple/Reuse/Batch 外层,先查二级缓存、未命中再委托内层真正执行。
  • 追问:批量插入 1 万条为什么用 BatchExecutor 快很多? 它用 JDBC 的 addBatch/executeBatch 攒批,把 1 万次数据库交互(网络往返+事务开销)压缩成几次,减少交互次数,性能可提升几十倍。

八、加强记忆

Executor 是 MyBatis 真正执行 SQL 的执行引擎(还管着一级缓存、事务),三种基础类型的区别在「怎么管理 Statement」:SimpleExecutor(默认)——每条 SQL 新建 Statement、执行完关闭、不复用,简单够用(缺点是相同 SQL 也重复预编译);ReuseExecutor——按 SQL 文本把 Statement 缓存在 Map 里、相同 SQL 复用(省去重复预编译,会话级,适合同一 SQL 高频执行);BatchExecutor——用 JDBC 的 addBatch/executeBatch 攒批批量发送,大批量写性能提升几十倍,但延迟执行带来陷阱(insert 不立即返回主键、中间不能混查询、必须 flush/commit 才真正执行)。此外 CachingExecutor 不是第四种,而是二级缓存的装饰器——包在上面三种外层,查询先看二级缓存、未命中再委托内层。选型:默认 Simple、批量 Batch、Reuse 少见。一级缓存就在 Executor 里(会话级)。一句话「Executor 是执行引擎,Simple 不复用、Reuse 复用 Statement、Batch 攒批快但延迟执行,CachingExecutor 装饰二级缓存」。