专栏名称: 京东科技技术说
京东科技官方技术公众号,传递最佳实践&技术创新。
目录
相关文章推荐
OSC开源社区  ·  LF开源软件学园五周年:感恩相伴,携手前行 ·  昨天  
极客之家  ·  更快更隐私,一款可以取代 Postman ... ·  3 天前  
OSC开源社区  ·  全球首个基于Web的“液态玻璃(Liquid ... ·  2 天前  
码农翻身  ·  强烈建议尽快搞个软考证!(重大利好) ·  2 天前  
51好读  ›  专栏  ›  京东科技技术说

大促数据库压力激增,如何一眼定位 SQL 执行来源?

京东科技技术说  · 公众号  · 程序员  · 2025-06-10 10:27

正文

请到「今天看啥」查看全文


{ /** * 处理 Statement 对象并返回结果对象 * * @param stmt SQL 语句执行后返回的 Statement 对象 * @return 映射后的结果对象列表 */ List handleResultSets (Statement stmt) throws SQLException; /** * 处理 Statement 对象并返回一个 Cursor 对象 * 它用于处理从数据库中获取的大量结果集,与传统的 List 或 Collection 不同,Cursor 提供了一种流式处理结果集的方式, * 这在处理大数据量时非常有用,因为它可以避免将所有数据加载到内存中 * * @param stmt SQL 语句执行后返回的 Statement 对象 * @return 游标对象,用于迭代结果集 */ Cursor handleCursorResultSets (Statement stmt) throws SQLException; /** * 处理存储过程的输出参数 * * @param cs 存储过程调用的 CallableStatement 对象 */ void handleOutputParameters (CallableStatement cs) throws SQLException; }
  • Executor : 它的方法很多,概括来说它负责数据库操作,包括增删改查等基本的 SQL 操作、管理缓存和事务的提交与回滚,所以拦截器切入它主要是 管理执行过程或事务

public interface Executor {    ResultHandler NO_RESULT_HANDLER = null;    // 该方法用于执行更新操作(包括插入、更新和删除),它接受一个 `MappedStatement` 对象和更新参数,并返回受影响的行数    int update(MappedStatement ms, Object parameter) throws SQLException;    // 该方法用于执行查询操作,接受 `MappedStatement` 对象(包含 SQL 语句的映射信息)、查询参数、分页信息、结果处理器等,并返回查询结果的列表     List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,                      CacheKey cacheKey, BoundSql boundSql) throws SQLException;     List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)            throws SQLException;     Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;    // 该方法用于刷新批处理语句并返回批处理结果    List flushStatements() throws SQLException;    // 该方法用于提交事务,参数 `required` 表示是否必须提交事务    void commit(boolean required) throws SQLException;    // 该方法用于回滚事务。参数 `required` 表示是否必须回滚事务    void rollback(boolean required) throws SQLException;    // 该方法用于创建缓存键,缓存键用于标识缓存中的唯一查询结果    CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);    // 该方法用于检查某个查询结果是否已经缓存在本地    boolean isCached(MappedStatement ms, CacheKey key);    // 该方法用于清空一级缓存    void clearLocalCache();    // 该方法用于延迟加载属性    void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class> targetType);    // 该方法用于获取当前的事务对象    Transaction getTransaction();    // 该方法用于关闭执行器。参数 `forceRollback` 表示是否在关闭时强制回滚事务    void close(boolean forceRollback);    boolean isClosed();    // 该方法用于设置执行器的包装器    void setExecutorWrapper(Executor executor);}
  • StatementHandler : 它的主要职责是准备( prepare )、“承接”封装 SQL 执行参数的逻辑,执行SQL( update / query )和“承接”处理结果集的逻辑,这里描述成“承接”的意思是这两部分职责并不是由它处理,而是分别由 ParameterHandler ResultSetHandler 完成,所以拦截器切入它主要是 在准备和执行阶段对 SQL 进行加工等

public interface StatementHandler {    Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException;    void parameterize(Statement statement) throws SQLException;    void batch(Statement statement) throws SQLException;    int update(Statement statement) throws SQLException;     List query(Statement statement, ResultHandler resultHandler) throws SQLException;     Cursor queryCursor(Statement statement) throws SQLException;    BoundSql getBoundSql();    ParameterHandler getParameterHandler();}

为了加深大家对这四个处理器的理解,了解它在查询 SQL 执行时作用的时机,我们来看一下查询 SQL 执行时的流程图:

image.png

每个声明 SQL 查询语句的 Mapper 接口都会被 MapperProxy 代理,接口中每个方法都会被定义为 MapperMethod 对象,借助 PlainMethodInvoker 执行(动态代理模式和策略模式), MapperMethod 中组合了 SqlCommand MethodSignature SqlCommand 对象很重要,它的 SqlCommand#name 字段记录的是 MappedStatement 对象的 ID 值(eg: org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAuthor),根据它来获取唯一的 MappedStatement (每个 MappedStatement 对象对应 XML 映射文件中一个 , , , 或 标签定义), SqlCommand#type 字段用来标记 SQL 的类型。当方法被执行时,会先调用 SqlSession 中的查询方法 DefaultSqlSession#selectOne ,接着由 执行器 Executor 去承接,默认类型是 CachingExecutor ,注意在这里它会调用 MappedStatement#getBoundSql 方法获取 BoundSql 对象,这个对象实际上最终都是在 StaticSqlSource#getBoundSql 方法中获取的,也就是说 此时我们定义在 Mapper 文件中的 SQL 此时已经被解析、处理好了(动态标签等内容均已被处理) ,保存在了 BoundSql 对象中。此时,要执行的 SQL 已经准备好了,它会接着调用 SQL 处理器 StatementHandler#prepare 方法创建与数据库交互的 Statement 对象,其中记录了要执行的 SQL 信息 ,而封装 SQL 的执行参数则由 参数处理器 DefaultParameterHandler TypeHandler 完成, ResultSet 结果的处理:将数据库中数据转换成所需要的 Java 对象由 结果处理器 DefaultResultSetHandler 完成。

现在我们对拦截器的原理和查询 SQL 的执行流程已经有了基本的了解,回过头来再想一下我们的需求:“使用 Mybatis 的拦截器在 SQL 执行前进行打标”,那么我们该选择哪个方法作为切入点更合适呢?

理论上来说在 Executor , StatementHandler ParameterHandler 相关的方法中切入都可以,但实际上我们还要多考虑一步: ParameterHandler 是用来处理参数相关的,在这里切入一般我们是要对入参 SQL 的入参进行处理,所以不选择这里避免为后续同学维护时增加理解成本; Executor “有时不是很合适”,它其中有两个 query 方法,先被执行的方法, 对应图中 CacheExecutor 左侧的直线 query 方法 Executor#query(MappedStatement, Object, RowBounds, ResultHandler)







请到「今天看啥」查看全文