JAVA MYSQL sql_calc_found_rows和found_rows()实践
一、背景
1.百万级数据库,数据量持续增加。每张数据表的字段数大于50(时间字段,分组字段,指标字段)
2.JDBCTemplate,java,mysql
二、问题描述
通过分析接口返回数据响应时间过长(通过某个分组字段搜索数据,响应时间长达30s)。
三、检查问题
检查代码,发现代码中运行了两句SQL语句,一句通过select
查询数据,一句通过select count(1)
来获取返回数据的总条数。
通过navicat查询语句对应的执行时间。
SELECT eventtime,smart_card_id,uevt_1000 FROM analytics_vhsession_user_event_info_day_201901 WHERE eventtime>='2019-01-01 00:00:00' AND eventtime<'2019-01-31 23:59:59' AND smart_card_id = '0382205801' ORDER BY eventtime asc LIMIT 0,1000
> OK
> 时间: 10.603s
SELECT count(1) FROM analytics_vhsession_user_event_info_day_201901 WHERE eventtime>='2019-01-01 00:00:00' AND eventtime<'2019-01-31 23:59:59' AND smart_card_id = '0382205801'
> OK
> 时间: 11.13s
同样耗时10s+,所以想办法从select count(1)
入手,减少SQL执行时间以达到减少响应时间的目的。
四、查询资料
通过查询资料,可以通过使用sql_calc_found_rows
和found_rows()
替代select count(1)
。
通过navicat查询语句对应的执行时间。
SELECT sql_calc_found_rows eventtime,smart_card_id,uevt_1000 FROM analytics_vhsession_user_event_info_day_201901 WHERE eventtime>='2019-01-01 00:00:00' AND eventtime<'2019-01-31 23:59:59' AND smart_card_id = '0382205801' ORDER BY eventtime asc LIMIT 0,1000
> OK
> 时间: 11.606s
SELECT FOUND_ROWS()
> OK
> 时间: 0.004s
相较之前的方案,响应时间可以减少10s以上,是一个值得尝试的方案。
五、优化尝试
根据之前的测试结果尝试进行代码优化,使用jdbcTemplate来调用两次query(),一次获取数据,一次获取对应的总条数。
//select sql_calc_found_rows
String selectSQL = "select sql_calc_found_rows ...";
List<Map<String, Object>> data = jdbcTemplate.queryForList(selectSQL);
//select found_rows()
String selectTotalCountSQL = "select found_rows()";
Long totalCount = jdbcTemplate.queryForObject(selectTotalCountSQL, Long.class);
但是在实际测试中遇到了jdbcTemplate.query("select found_rows()")
返回的总条数与实际的总条数不一致的情况。 通过查询相应的资料,在一篇分享文档发现一点端倪,以下为资料原文:
we do this by opening a connection, running two SELECT queries, then closing the connection. This allows us to achieve the desired result that we need.
sql_calc_found_rows
和found_rows()
需要两句SQL在同一会话中,才能保证select found_rows()
返回的总条数是上一句select sql_calc_found_rows
对应的总条数
查看jdbcTemplate.query()底层代码实现。
public <T> T execute(StatementCallback<T> action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");
Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this.nativeJdbcExtractor != null &&
this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this.nativeJdbcExtractor != null) {
stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
}
T result = action.doInStatement(stmtToUse);
handleWarnings(stmt);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
}
finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
jdbcTemplate每次执行query()都会从连接池中获取连接
Connection con = DataSourceUtils.getConnection(getDataSource())
执行完成后释放连接
DataSourceUtils.releaseConnection(con, getDataSource());
不能保证两次query()
在一个会话中(同一个Connection)。
六、优化实践
优化方案:不使用JDBCTemplate中的query()方法,自己实现具体逻辑。通过DataSourceUtils.getConnection(jdbcTemplate.getDataSource())
获取会话,使用Statement
来执行两次SQL后,再通过DataSourceUtils.releaseConnection(conn, jdbcTemplate.getDataSource());
释放会话,保证两句SQL在同一会话中。
public PagedArrayList getDataAndTotalCount(String sql){
Connection conn = null;
Statement statement = null;
ResultSet rs = null;
ResultSet rs1 = null;
long totalCount = 0L;
PagedArrayList data = new PagedArrayList();
try {
conn = DataSourceUtils.getConnection(jdbcTemplate.getDataSource());
conn.setAutoCommit(true);
statement = conn.createStatement();
rs = statement.executeQuery(sql);
ResultSetMetaData md = rs.getMetaData(); //获得结果集结构信息,元数据
int columnCount = md.getColumnCount(); //获得列数
while (rs.next()) {
Map<String,Object> rowData = new HashMap<String,Object>();
for (int i = 1; i <= columnCount; i++) {
rowData.put(md.getColumnName(i), rs.getObject(i));
}
data.add(rowData);
}
String totalCountSQL = "select found_rows() AS total_count";
rs1 = statement.executeQuery(totalCountSQL);
while (rs1.next()){
totalCount = rs1.getLong("total_count");
}
data.totalCount = totalCount;
} catch (Exception e) {
slf4jLogger.error("getDataAndTotalCount() error:", e);
} finally {
//关闭资源
JdbcUtils.closeResultSet(rs);
JdbcUtils.closeResultSet(rs1);
JdbcUtils.closeStatement(statement);
//释放资源
DataSourceUtils.releaseConnection(conn, jdbcTemplate.getDataSource());
}
return data;
}
七、参考文档

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
会写代码是你创业路上的包袱
最近没上班, 大部分时间都在搞一个开源软件: github:“想学吗”个人知识管理工具 gitee:“想学吗”个人知识管理工具 (请大家多多支持) 前两天跟一个朋友吃饭,聊到创业的话题 忽然想起我2017年写的这篇博客, 于是增补了一些内容,外加对原有内容做了整理,发布到OSC上。 第一个问题:敢不敢迈出第一步 可能很多程序员都认真考虑过创业的问题, 但最终还是放弃了, 很大一部分原因,就是觉得自己有一技之长。 但他们往往不这么说,他们会说: 创业风险太大,要还房贷啦,要存奶粉钱啦,要照顾家里人啦, 种种借口; 其实归根结底,还是因为有一技之长,有口饭吃,让他迈不出这一步。 实事就是:他停在他的舒适区里,惧怕生活模式的变化。 想想看,假设你不会写代码,是不是会更想创业呢? 你会去富士康打工,当个文员,还是自己开个奶茶店呢? 世界上那么多你嗤之以鼻的创业方式,为什么都活的好好的呢? 第二个问题:潜意识里过于追求“安全” 程序员对安全的追求会导致他在创业活动中难以容纳足够多的风险 我们学写程序的第一天开始,安全的观念就植根在我们的内心深处了, 你想想看: 不能有内存泄漏; 递归不能有退...
-
下一篇
超实用的 Nginx 极简教程,覆盖了常用场景
概述 什么是 Nginx? Nginx (engine x)是一款轻量级的 Web 服务器 、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 什么是反向代理? 反向代理(Reverse Proxy)方式是指以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。 安装与使用 安装 详细安装方法请参考:Nginx 安装 使用 nginx 的使用比较简单,就是几条命令。 常用到的命令如下: nginx -s stop 快速关闭Nginx,可能不保存相关信息,并迅速终止web服务。 nginx -s quit 平稳关闭Nginx,保存相关信息,有安排的结束web服务。 nginx -s reload 因改变了Nginx相关配置,需要重新加载配置而重载。 nginx -s reopen 重新打开日志文件。 nginx -c filename 为 Nginx 指定一个配置文件,来代替缺省的。 nginx -t 不运行,而...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker容器配置,解决镜像无法拉取问题
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8编译安装MySQL8.0.19
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- Hadoop3单机部署,实现最简伪集群
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- MySQL8.0.19开启GTID主从同步CentOS8
- Red5直播服务器,属于Java语言的直播服务器