是的,列数会间接影响性能。 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 的增加