我正在开发一个使用服务定位器模式的项目,它还有一个静态类,其中包含我们用来执行所有数据库事务的 DataSource 对象。一般设置类似于以下代码片段:
public class Environment {
//multiple app instances on one server
private static final HashMap<String, DataSource> appDatasources = new HashMap<>();
public static DataSource getDataSource(String appName){
return appDatasources.get(appName);
}
public static DataSource getDataSource() {
return appDatasources.get(getApplicationName());
}
public static String getApplicationName(){
return ServiceLocator.getAppName();
}
public static void createDatasource(String jdbc, String appName){
org.apache.tomcat.dbcp.dbcp.BasicDataSource ds = new org.apache.tomcat.dbcp.dbcp.BasicDataSource();
ds.setDriveClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
ds.setUrl(jdbc);
ds.setMaxActive(100);
ds.setMaxIdle(50);
ds.setInitialSize(10);
ds.setRemoveAbandoned(true);
ds.setRemoveAbandonedTimeout(10);
ds.setLogAbandoned(true);
appDatasources.put(appName, ds);
}
}
}
public class ServiceLocatorFactory(String appName) {
public static void registerServices(){
DataSource ds = Environment.getDataSource(appName);
ServiceLocator.replaceService("Service", new Service(ds);
...
...
}
}
我们将 DataSource 传递到我们的服务中,它们被传递到我们的数据访问对象并存储为成员变量,它应该是对我们在 Environment
中创建的同一个 DataSource 对象的引用。
public class Service {
private ServiceDAO dao;
public Service(DataSource ds){
dao = new ServiceDAO(ds);
}
}
public class ServiceDAO extends AbstractDAOService{
public ServiceDAO(DataSource ds){
super(ds);
}
}
public abstract class AbstractDAOService {
private final DataSource datasource;
public AbstractDAOService(DataSource ds){
this.datasource = ds;
}
protected DataSource getDataSource(){
return this.datasource;
}
protected int queryGetCount(ParameterQuery qry, String countColName){
ConnectionQuery q = new ConnectionQuery(getDataSource(), qry);
int retval = 0;
try {
ResultSet rs = q.getResultSet();
try {
if(rs.next()){
retval = rs.getInt(countColName);
}
} finally {
rs.close();
}
} catch(Exception ex) {
//handle exception
}
q.close();
return retval;
}
}
连接查询对象从数据源获取一个连接,然后做一个prepared statement并执行它得到结果集。我们遇到的问题是,有一个查询在通过此管道运行时需要花费大量时间才能完成。我们已经看到一个相当简单的查询的执行时间从 17 秒到 90(!)秒不等。当通过 SQL Server Management Studio 执行时,查询以毫秒为单位运行。我们添加了日志记录,以确定代码中发生故障的确切位置,以及速度缓慢的原因是 PreparedStatament.execute()
。
我们注意到,如果我们将 AbstractDAOSerice
中的 getDataSource()
方法替换为以下内容
protected DataSource getDataSource(){
return Environment.getDataSource();
}
然后查询的执行速度与通过 SSMS 执行时的速度相同。据我们了解,这两个解决方案应该引用同一个对象,因此在解决问题的同时,我们希望更好地理解为什么这是一个问题,以及我们的更改如何解决问题.非常感谢 Java 专家的任何指导。
作为引用,我们在 Java jdk1.8.0_144 上
编辑:
public class ConnectionQuery {
private Connection con;
private PreparedStatement stmt;
private ResultSet rs;
private final DataSource datasource;
private ParameterQuery qry;
public ConnectionQuery(DatasSource ds, ParamterQuery qry) {
this.datasource = ds;
this.qry = qry;
}
public ResultSet getResultSet() throws SQLException {
try {
Connection c = defineConnection(true);
this.stmt = this.getQuery.makeStatement(c, false);
this.stmt.execute();
this.rs = this.stmt.getResultSet();
} catch(SQLException se) {
this.close();
throw se;
}
return this.rs;
}
private Connection defineConnection(boolean readOnly) throws SQLException {
if(this.con == null || this.con.isClosed()) {
this.con = this.datasource.getConnection();
}
this.con.setReadOnly(readOnly);
if(this.transactionIsolation != 777){
this.con.setTransactionIsolation(this.transactionIsolation);
}
return this.con;
}
/**
* close all parts of the connection, the RecordSet, the Statement, and the Connection
*/
public void close() {
if(this.rs != null) {
try{
this.rs.close();
this.rs = null;
} catch (SQLException e) {
//warn
}
}
if(this.stmt != null) {
try{
this.stmt.close();
this.stmt = null;
} catch (SQLException e) {
//warn
}
}
if(this.con != null) {
try{
this.con.close();
this.con = null;
} catch (SQLException e) {
//warn
}
}
}
}
最佳答案
我们最终找到了这个问题的解决方案,并在此处更新以防其他人遇到同样的问题。 Java 代码更改是转移注意力,与修复查询性能无关。
我们遇到的是 Parameter Sniffing .
此问题的简要描述是,当 SQL 缓存查询的执行计划时使用一组它可以正常工作的参数,然后使用导致主要性能问题的不同参数集重新使用该执行计划。 Brent Ozar 在上面的链接中更深入地讨论了这个问题。
我们遇到这个问题并且代码更改实际上确实让它看起来像工作的原因是因为我们正在运行的查询用于确定我们系统中的某些对象是否正在被最终用户使用。如果我们检查的第一个对象未在使用中,那么 SQL 将缓存一个没有结果的对象的执行计划,并且当用于具有超过 14000 个结果的对象(在具有 108000 行的表上)时,查询花费的时间会大大增加。我们强制我们的具有较大结果集的查询与其他查询的执行计划一起运行,并在 SQL Server Management Studio 中重现它以确保我们的发现毫无疑问。
我们在项目中推进的解决方案是重新访问和优化查询/表结构,以便执行计划在该查询的不同输入参数之间保持不变,并且执行时间也保持一致。
希望这些信息对您有所帮助。
关于java - 通过 JDBC 连接的 SQL 查询速度较慢,具体取决于访问数据源对象的位置,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52704659/