• Oracle ——数据库 SQL 分页性能分析


    本文内容

    • 创建测试表 test 及其数据
    • 创建 test 表索引
    • 创建 test 表的统计数据
    • 测试数据分布情况
    • 演示三种数据库分页的性能
    • 总结
    • 参考资料

    本文内容并不新鲜,很早就有人写过,但那毕竟是别人的。还是自己动手看看较好。本文使用 Oracle 11g Release 1 (11.1)。

    另外,执行计划和统计信息的具体含义,参看本文最后的参考资料链接。

    创建测试表 test 及其数据


    利用 Oracle 自己的视图,伪造一个测试表,并插入测试数据。即便是新装的 Oracle,也会有将近 56000 行数据。测试表有四个字段,分别表示类别 ID,产品名称,价格和供应商。

    SQL> drop table test
    2 /
     
    Table dropped
     
    SQL> create table test
    2 as select mod(object_id,4)*10 category_id,
    3 object_name product_name,
    4 object_id price,
    5 rpad('a',5,'b') supplier
    6 from all_objects order by 2,1
    7 /
     
    Table created
     
    SQL>

    创建 test 表索引


    演示数据库分页时,我们使用了 category_id 为过滤条件,并用 product_name 字段排序,所以为它们建立组合索引。

    SQL> create index test_cid_pname on test(category_id,product_name)
    2 /
     
    Index created
     
    SQL>

    创建 test 表的统计数据


    利用 ANALYZE 语句统计 test 表,和其所有索引以及索引列。

    SQL> analyze table test compute statistics
      2  for table
      3  for all indexes
      4  for all indexed columns
      5  /
     
    Table analyzed
     
    SQL> select t.table_name,
      2         t.num_rows,
      3         t.blocks,
      4         t.empty_blocks,
      5         t.avg_space,
      6         t.avg_row_len,
      7         t.sample_size,
      8         t.last_analyzed
      9    from user_tables t
     10   where T.table_name = 'TEST'
     11  /
     
    TABLE_NAME    NUM_ROWS       BLOCKS EMPTY_BLOCKS  AVG_SPACE AVG_ROW_LEN SAMPLE_SIZE LAST_ANALYZED
    ---------- ---------- ---------- ------------ ---------- ----------- ----------- --------------------
    TEST                65058           821              75           863             86         65058     2012-9-10 11:02:45
     
    SQL>

    测试数据分布情况


    在演示数据库分页前,先大概了解数据情况。已上面方式创建的测试表和其数据,即便是新安装的 Oracle 数据库,也又差不多 6 万条数据。

    分析后,可以看出 test 表,有 65058 行,使用了 821 个数据块,平均行长度为 86。

    执行下面 SQL 语句,查看一下各个类别的数据都有多少条。类别是之后分页的其中一个条件。

    SQL> select t.category_id, count(t.category_id) as categorycount
      2    from test t
      3   group by t.category_id
      4   order by t.category_id asc
      5  /
     
    CATEGORY_ID CATEGORYCOUNT
    ----------- -------------
              0         16266
             10         16290
             20         16283
             30         16219
     
    SQL>

    测试表的数据,有四个类别,每个类别都有 1.6 万条数据。

    演示三种数据库分页的性能


    下面所有 SQL 语句中的 category_id 为类别 ID;beginrno 为开始行号;endrno 为结束行号。它们都是需要输入的回话变量。

    作法 1

    1, 先选择指定类别的所有行,并排序。

    2, 再在此内部视图选择开始行号 beginrno 和结束行号 endrno 之间的所有数据。

    image

    image

    image

    执行计划是“倒”着看的。

    从执行计划上看,从始至终数据库都处理类别为 0 所有的数据行(16266 行)。在过滤类别时,进行了全表扫描,文中开始创建索引没有使用。最后为了获得指定开始和结束行号的数据。

    这显然很多余。因为,既然是分页,我们只关心开始行号和结束行号之间的数据,至少,刚开始,只关心结束行号之前的数据。另外,cost 列值也不小,为了排序还使用了 1608K 的临时表空间。这要是在互联网上还了得。

    统计信息也反映出,consistent gets 值很大,接下来的 sorts(memory) 值还为 1。该 SQL 语句造成 Oracle 在内存中排序 16266 行数据。

    其中,category_id 为 类别ID;beginrno 为开始行号;endrno 为结束行号。

    作法 2

    既然没必要全部获,就可以先获得 endrno 之前的数据,再利用 beginrno 截取。

    1, 先选择指定类别的行,并排序。

    2, 再次内部视图选择 endrno 之前的所有行。

    3, 最后利用 beginrno 选择,从而得到 beginrnoendrno 之间的行。

    image

    image

    image

    在执行计划中,从 ROWS、COST、TIME 看上去比作法 1 好多了,数值明显减少。其中,STOPKEY 起了重要作用。它为了 TOP n 操作做了优化,即本例 SQL 的内部语句:

    select rownum rno, a.*
      from (select *
              from test
             where category_id = &category_id
             order by product_name) a
     where rownum <= &endrno

    从统计信息看,consistent gets 值也减少了。sorts(memory) 值是 0。

    作法 3

    根据作法 2,选择过滤条件以及开始行号和结束行号,获得行 ROWID,再根据获得制定行 ROWID 的记录。这种看起来有点多余,但的确有效。

    image

    image

    image

    总结


    如下表所示,是本文使用数据库分页的三种形式。

    表 1 三种数据库分页

    作法

    SQL 语句

    描述

    作法 1

    select *
       from (select rownum rno, a.*
               from (select *
                       from test
                      where category_id = &category_id
                      order by product_name) a)
      where rno between &beginrno and &endrno
     

    作法 2

    select *
      from (select rownum rno, a.*
              from (select *
                      from test
                     where category_id = &category_id
                     order by product_name) a
             where rownum <= &endrno)
     where rno >= &beginrno
     

    作法 3

    select *
      from test
     where rowid in (select rid
                       from (select rownum rno, rowid rid
                               from (select rowid
                                       from test
                                      where category_id = &category_id
                                      order by product_name)
                              where rownum <= &endrno)
                      where rno >= &beginrno)
     

    如下表所示,是本文三种数据库分页方式的统计信息。

    表 2 三种数据库分页的统计信息

    统计信息

    作法1

    作法2

    作法3

    recursive calls

    0

    1

    1

    db block gets

    0

    0

    0

    consistent gets

    804

    234

    28

    physical gets

    0

    0

    0

    redo size

    0

    0

    0

    bytes sent via SQL*Net to client

    1154

    1154

    1058

    bytes received via SQL*Net from client

    416

    416

    416

    SQL*Net roundtrips to/from client

    3

    4

    3

    sorts(memory)

    1

    0

    0

    sorts(disk)

    0

    0

    0

    rows processed

    11

    11

    11

    参考资料


  • 相关阅读:
    Yii AR Model 查询
    学习进度4
    学习进度三
    个人每日总结7
    个人每日总结6
    个人每日总结5
    个人每日总结4
    个人冲刺承担的任务项目的用户模板和用户场景模板
    个人每日总结3
    个人每日总结2
  • 原文地址:https://www.cnblogs.com/liuning8023/p/2680901.html
Copyright © 2020-2023  润新知