美文网首页
mysql limit 优化和流查询

mysql limit 优化和流查询

作者: duval | 来源:发表于2017-03-30 21:20 被阅读0次

    遇到一个需求是需要将整个表的数据逐行读取出来进行处理。

    使用 limit

    一开始实现是使用limit来做,每个循环结束后将offset+= 100,如下:

    PreparedStatement pStatement = dm.prepareStatement("SELECT start_time,input_params FROM execution_jobs limit ? , ? ";
    pStatement.setInt(offset, 100);
    ResultSet rs = pStatement.executeQuery();
    

    这个方法在offset变得越来越大之后,查询会非常缓慢。因为 select * from XXX limit 10000,10; 相当于扫描了满足条件的前10000行之后,丢掉,然后再读取10行。所以性能会非常差。

    针对limit 查询性能慢的优化办法有很多。下面详细介绍:

    • 子查询法
       先找出第一条数据,然后大于等于这条数据的id就是要获取的数据
       缺点:数据必须是连续的,可以说不能有where条件,where条件会筛选数据,导致数据失去连续性
    mysql> set profiling=1;                             # 开启profile
    Query OK, 0 rows affected (0.00 sec)
    
    mysql> pager grep !~-                              #关闭stdout
    PAGER set to 'grep !~-'
    mysql> select exec_id ,project_id from execution_jobs limit 100000,100;
    100 rows in set (2.65 sec)
    
    mysql> select exec_id ,project_id from execution_jobs where exec_id >= (select exec_id from execution_jobs  limit 100000,1) limit 100;
    100 rows in set (0.52 sec)
    
    mysql> nopager                                   #打开stdout
    PAGER set to stdout
    mysql> show profiles;                          #查看耗时
    +----------+------------+---------------+----------------+--------------------------------------------------------------------------------------------------------------------------------+
    | Query_ID | Duration   | Logical_reads | Physical_reads | Query                                                                                                                          |
    +----------+------------+---------------+----------------+--------------------------------------------------------------------------------------------------------------------------------+
    |        1 | 2.67572900 |         13337 |            410 | select exec_id ,project_id from execution_jobs limit 100000,100                                                                |
    |        2 | 0.53610725 |         13026 |            264 | select exec_id ,project_id from execution_jobs where exec_id >= (select exec_id from execution_jobs  limit 100000,1) limit 100 |                                                                                                       |
    +----------+------------+---------------+----------------+--------------------------------------------------------------------------------------------------------------------------------+
    3 rows in set (0.01 sec)
    
    
    • 倒排表优化法

      倒排表法类似建立索引,用一张表来维护页数,然后通过高效的连接得到数据
       缺点:只适合数据数固定的情况,数据不能删除,维护页表困难

    • 反向查找优化法
       当偏移超过一半记录数的时候,先用排序,这样偏移就反转了
       缺点:order by优化比较麻烦,要增加索引,索引影响数据的修改效率,并且要知道总记录数,偏移大于数据的一半

    • limit限制优化法
      把limit偏移量限制低于某个数,超过这个数等于没数据。

    使用Mysql的流式查询

    用上边的各种办法优化limit固然可行,但使用mysql的流查询是更加优越的实现这个需求的办法。

    默认情况下,JDBC去操作mysql的时候,select语句都是将所有的结果缓存到内存中,但是采用流式查询的话,可以设置一个抓取数值,每次只读取一小部分数据。实现办法是:

    PreparedStatement pStatement = dm.prepareStatement("SELECT exec_id,project_id  FROM execution_jobs", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    pStatement.setFetchSize(Integer.MIN_VALUE);   
    ResultSet rs = pStatement.executeQuery();
    
    while (rs.next()) {
    // do something
    }
    
    

    这里 pStatement.setFetchSize(Integer.MIN_VALUE) 会让人困惑。还是直接看mysql的源码:

    /**
     * We only stream result sets when they are forward-only, read-only, and the
     * fetch size has been set to Integer.MIN_VALUE
     *
     * @return true if this result set should be streamed row at-a-time, rather
     * than read all at once.
     */
    protected boolean createStreamingResultSet() {
        try {
            synchronized(checkClosed().getConnectionMutex()) {
                return ((this.resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY)
                     && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this.fetchSize == Integer.MIN_VALUE));
            }
        } catch (SQLException e) {
            // we can't break the interface, having this be no-op in case of error is ok
    
            return false;
        }
    }
    

    从源码我们可以看到,只有forward-only, read-only, fetchsize为Integer.MIN_VALUE 三者同时成立,才会开启流查询方式。可以参考这里的回答:
    http://stackoverflow.com/questions/20899977/what-and-when-should-i-specify-setfetchsize

    更多精彩:
    Duval的GithubIO

    相关文章

      网友评论

          本文标题:mysql limit 优化和流查询

          本文链接:https://www.haomeiwen.com/subject/odmpottx.html