(资料图)
use java mysql connector
// fake mysql select code// ... datasource initConnection conn = datasource.getConnection();PreparedStatement pst = conn.prepareStatement("select id, task_name from t_task where id = ?");pst.setLong(1, 31);pst.executeQuery();
按照直觉, 既然用了 preparedStatement, 执行过一次后会在服务端缓存好预编译的语句, 之后就能省去这个解析过程,直接提交参数执行就好了
- 但是, mysql connector 默认创建的是 ClientPreparedStatement
public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { try { synchronized(this.getConnectionMutex()) { this.checkClosed(); ClientPreparedStatement pStmt = null; boolean canServerPrepare = true; String nativeSql = (Boolean)this.processEscapeCodesForPrepStmts.getValue() ? this.nativeSQL(sql) : sql; if ((Boolean)this.useServerPrepStmts.getValue() && (Boolean)this.emulateUnsupportedPstmts.getValue()) { canServerPrepare = this.canHandleAsServerPreparedStatement(nativeSql); } // useServerPrepStmts = false if ((Boolean)this.useServerPrepStmts.getValue() && canServerPrepare) { // ... 省略一些代码 } else { //... 省略一些代码 pStmt = (ClientPreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } } } else { pStmt = (ClientPreparedStatement)this.clientPrepareStatement(nativeSql, resultSetType, resultSetConcurrency, false); } return (PreparedStatement)pStmt; } } catch (CJException var17) { throw SQLExceptionsMapping.translateException(var17, this.getExceptionInterceptor()); } }
因为默认并没有设置 useServerPrepStmts = true, 默认是false 去指定要求服务端缓存 创建的 clientPrepareStatement 客户端语句
- 是不是 ClientPreparedStatement 看着客户端侧进行一些元数据的缓存?pst.executeQuery(); 代码中有一段逻辑, 如果cacheResultSetMetadata=true的话,会缓存元数据,但是并没有
boolean cacheResultSetMetadata = (Boolean)locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue(); String origSql = ((PreparedQuery)this.query).getOriginalSql(); if (cacheResultSetMetadata) { cachedMetadata = locallyScopedConn.getCachedMetaData(origSql); }
所以,虽然每次还是从服务端拿返回 参数 和 resultSet 的一些元数据
最终发往服务端(这边场景是proxy) 的 sql 其实是非参数化的 com_query 命令
proxy 接收到 com_query 交由 MySQLComQueryPacketExecutor 处理
public Collection> execute() throws SQLException { ResponseHeader responseHeader = proxyBackendHandler.execute(); if (responseHeader instanceof QueryResponseHeader) { return processQuery((QueryResponseHeader) responseHeader); } responseType = ResponseType.UPDATE; return processUpdate((UpdateResponseHeader) responseHeader); }
MySQLComQueryPacketExecutor 常规文本查询,在 proxy frontend 处理过程较为的简单,直接交给 backend 执行,后续就和 sharding-jdbc 核心处理逻辑一致了,比如 分库分表、读写分离、单库单表直接执行
扩展:
- mysql connector 性能相关属性 https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-connp-props-performance-extensions.html
- mysql/server cache 对比 https://vladmihalcea.com/mysql-jdbc-statement-caching/