【问题标题】:Does number of columns in a table affect the performance of a count(*) query on a table with no indexes?表中的列数是否会影响对没有索引的表执行 count(*) 查询?
【发布时间】:2014-12-20 18:09:33
【问题描述】:

我正在创建基准表来测量 Netezza 机器上的每小时负载(查询延迟、查询持续时间)。我对表格中想要的行数有一个很好的了解,并且我正在尝试确定列数。不会有索引;我将运行全表扫描。

我正在尝试确定基准表中需要多少列并且有以下问题:列数(及其类型)将如何影响 count(*) 查询的性能。我最初的想法是具有更多列的表将分布在更多磁盘块上。因此,系统将不得不进行更多的磁盘寻道,从而导致更长的查询。

在我从事 Netezza 工作的同时,我也欢迎与其他系统(MySql、Postgres、Vertica 等)相关的答案,以帮助我大致了解。

关于列数对查询性能的影响,已经有多次讨论(Q1Q2Q3)。这些问题讨论的是通用查询,而不是没有索引的全表扫描。因此,单独的问题。

【问题讨论】:

    标签: mysql database performance postgresql


    【解决方案1】:

    是的,列数会间接影响性能。 in 列中的数据也会影响速度。

    这是为什么呢?

    每个 DBMS 都以块的形式存储行 - 通常是 8k 块,但不一定。特别是数据仓库系统倾向于使用更大的块大小。如果一个表有很多列,其中包含很多数据(想想varchar 列),这意味着更少的行适合单个数据库块。

    对于支持适当隔离的事务系统,count(*) 查询必须查询表中的所有行(并检查每一行是否当前对 您的 事务可见)。 DBMS 从硬盘读取的最小单位是块。因此,适合块的行越多,需要完成的 I/O 就越少。

    如果一行平均占用 100 个字节,则单个块将包含大约 80 行。要计算一个有 80 行的表中的所有行,数据库只需要执行一次 I/O 操作(实际上查找表本身要多一点)。

    现在,如果每行需要 1000 字节,则单个块包含大约 8 行,这反过来意味着计算数据库需要执行 8 次 I/O 操作的所有行。

    即使数据被缓存,它仍然是 1 个“逻辑”I/O 与 8 个“逻辑”I/O 操作。

    只有在不涉及索引的情况下,上述情况才成立。

    任何支持详细执行计划的 DBMS 都可以观察到这种效果。为 Postgres 获取这个示例脚本:

    create table data5 (c1 text, c2 text, c3 text, c4 text, c5 text);
    insert into data5 
    select rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X')
    from generate_series(1,100000);
    
    create table data10 (c1 text, c2 text, c3 text, c4 text, c5 text, c6 text, c7 text, c8 text, c9 text, c10 text);
    insert into data10 
    select rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X'),
           rpad('X',50,'X')
    from generate_series(1,100000);
    

    上面创建了两个表,每个表有 100.000 行。一个有 5 列,另一个有 10 列。

    进行解释分析时,将返回以下内容(Postgres 9.3):

    explain (analyze, buffers, verbose)
    select count(*)
    from data5;
    
    Aggregate  (cost=4192.00..4192.01 rows=1 width=0) (actual time=27.539..27.539 rows=1 loops=1)
      Output: count(*)
      Buffers: shared hit=2942
      ->  Seq Scan on stuff.data5  (cost=0.00..3942.00 rows=100000 width=0) (actual time=0.005..16.158 rows=100000 loops=1)
            Output: c1, c2, c3, c4, c5
            Buffers: shared hit=2942
    Total runtime: 27.595 ms
    

    Buffers: shared hit=2942 行告诉我们 Postgres 必须查看 2942 个块才能读取整个表。

    现在是 10 列的表格:

    explain (analyze, buffers, verbose)
    select count(*)
    from data10;
    
    Aggregate  (cost=7917.00..7917.01 rows=1 width=0) (actual time=34.964..34.965 rows=1 loops=1)
      Output: count(*)
      Buffers: shared hit=6667
      ->  Seq Scan on stuff.data10  (cost=0.00..7667.00 rows=100000 width=0) (actual time=0.010..22.187 rows=100000 loops=1)
            Output: c1, c2, c3, c4, c5, c6, c7, c8, c9, c10
            Buffers: shared hit=6667
    Total runtime: 35.025 ms
    

    我们可以看到 Postgres 必须查看 6667 个块才能获得计数。

    使用 Oracle 的 SQL*Plus 和 set autotrace statistics 选项可以观察到类似的情况,该选项也将显示已执行的(逻辑)I/O 数量。

    data5 表的统计信息如下所示:

    VALUE | STATISTIC                             
    ------+---------------------------------------
      140 | bytes received via SQL*Net from client
      755 | bytes sent via SQL*Net to client      
     2977 | consistent gets                       
        0 | db block gets                         
        0 | physical reads                        
        0 | recursive calls                       
        0 | redo size                             
     2977 | session logical reads                 
        1 | sorts (memory)                        
        2 | SQL*Net roundtrips to/from client     
        1 | rows processed                        
    

    “一致获取”表示逻辑 I/O 的数量:

    对于data10表,输出如下:

    VALUE | STATISTIC                             
    ------+---------------------------------------
      141 | bytes received via SQL*Net from client
      615 | bytes sent via SQL*Net to client      
     7184 | consistent gets                       
        0 | db block gets                         
        0 | physical reads                        
        0 | recursive calls                       
        0 | redo size                             
     7184 | session logical reads                 
        1 | sorts (memory)                        
        2 | SQL*Net roundtrips to/from client     
        1 | rows processed                        
    

    我们再次清楚地看到(逻辑)I/O 的增加

    【讨论】:

    • 作为脚注:此答案的解释适用于使用count(1)count(*) 时。重要的是 table 中的列,而不是提供给聚合函数的参数。
    • @Allan count(1)count(*) 之间没有区别。 count(some_column) 的答案也是正确的,因为当没有索引存在时,DBMS 仍然需要读取和检查表中的每一行(count(some_column) 相当于 count(*) where some_columns is not null
    • 是的,这正是我要说的。考虑到关于 count(*) 的神话有多普遍,我认为添加一个快速注释会很有用,即更改函数不会导致这种情况下的改进。
    猜你喜欢
    • 1970-01-01
    • 2017-03-22
    • 1970-01-01
    • 2015-11-28
    • 2021-01-29
    • 2016-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多