MyBatis 的 Executor 有哪几种?SimpleExecutor、ReuseExecutor、BatchExecutor 有什么区别?
简化版
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 装饰二级缓存」。