更新时间:2026-07-28 GMT+08:00
分享

单列统计信息

行数和页面数

行数和页面数描述整表的统计信息,reltuples表示该表包含多少行元组,relpages表示该表包含多少个页面。普通表从pg_class中查询,分区表的分区从pg_partition中查询。行数和页面数用于估算查询结果的大小,通常会结合其他统计信息(如NDV、MCV、直方图等)一起使用,以便更全面地了解数据的分布和存储情况。

行数和页面数查询示例:

-- 统计信息章节计划显示均使用normal模式。
gaussdb=# SET explain_perf_mode = normal;
SET
gaussdb=# CREATE TABLE t1(a int, b int);
CREATE TABLE
gaussdb=# INSERT INTO t1 VALUES(generate_series(1, 1000), generate_series(1, 1000));
INSERT 0 1000
gaussdb=# ANALYZE t1;
ANALYZE
gaussdb=# SELECT reltuples,relpages FROM pg_class where relname = 't1';
 reltuples | relpages
-----------+----------
      1000 |        5
(1 row)

gaussdb=# CREATE TABLE test_range_pt (a int, b int, c int)
PARTITION BY range(a)
(
PARTITION p1 VALUES LESS THAN(100),
PARTITION p2 VALUES LESS THAN (200),
PARTITION p3 VALUES LESS THAN (300),
PARTITION p4 VALUES LESS THAN (maxvalue)
)ENABLE ROW MOVEMENT;
CREATE TABLE
gaussdb=# INSERT INTO test_range_pt VALUES(generate_series(1, 1000), generate_series(1, 1000));
INSERT 0 1000
gaussdb=# ANALYZE test_range_pt;
ANALYZE
gaussdb=# SELECT reltuples,relpages FROM pg_partition WHERE relname = 'p1';
 reltuples | relpages
-----------+----------
        86 |        1
(1 row)
gaussdb=# DROP TABLE t1;
DROP TABLE
gaussdb=# DROP TABLE test_range_pt;
DROP TABLE

对于系统函数,优化器仅基于pg_proc.prorows/procost进行静态成本估算,不维护普通表统计信息,不保证估算行数与运行时实际返回行数一致。用户在查询系统函数时,应避免将高代价系统函数子查询直接参与复杂Join,尤其应避免与LIKE、正则、函数表达式等非等值条件组合使用。建议先将系统函数结果物化为临时表或CTE,再进行过滤和关联,以避免Nested Loop重复执行导致查询耗时过长。

NDV(number of distinct values)

NDV是指在数据库表的某一列中不同值的个数。例如,在员工表中,如果“性别”列只有“男”和“女”两个不同的值,那么该列的NDV为2。

对于数据库优化器来说,准确的NDV信息对执行计划选择至关重要。在表连接操作中,如果连接条件列的NDV很低(即重复值较多),优化器可能倾向于选择哈希连接等算法,这是因为对于大量重复值的列,哈希连接在构建哈希表时往往更高效。

NDV查询示例:

gaussdb=# CREATE TABLE Employee(name text, gender text);
CREATE TABLE
gaussdb=# INSERT INTO Employee VALUES('小明','男'),('小张','男'),('小华','女'),('小李','女');
INSERT 0 4
gaussdb=# ANALYZE Employee;
ANALYZE
gaussdb=# SELECT tablename,attname,n_distinct FROM pg_stats WHERE tablename='employee';
 tablename | attname | n_distinct
-----------+---------+------------
 employee  | name    |         -1
 employee  | gender  |        -.5
(2 rows)
gaussdb=# INSERT INTO Employee SELECT * FROM Employee;
INSERT 0 4
gaussdb=# INSERT INTO Employee SELECT * FROM Employee;
INSERT 0 8
gaussdb=# INSERT INTO Employee SELECT * FROM Employee;
INSERT 0 16
gaussdb=# ANALYZE Employee;
ANALYZE
gaussdb=# SELECT tablename,attname,n_distinct FROM pg_stats WHERE tablename='employee';
 tablename | attname | n_distinct
-----------+---------+------------
 employee  | name    |      -.125
 employee  | gender  |          2
(2 rows)
gaussdb=# SET enable_fast_query_shipping = off;
SET
gaussdb=# EXPLAIN SELECT * FROM Employee t1, Employee t2 WHERE t1.gender = t2.gender;
                                     QUERY PLAN
-------------------------------------------------------------------------------------
 Streaming (type: GATHER)  (cost=8.41..32.22 rows=512 width=22)
   Node/s: All datanodes
   ->  Hash Join  (cost=4.41..8.22 rows=512 width=22)
         Hash Cond: (t1.gender = t2.gender)
         ->  Seq Scan on employee t1  (cost=0.00..1.18 rows=32 width=11)
         ->  Hash  (cost=4.01..4.01 rows=64 width=11)
               ->  Streaming(type: BROADCAST)  (cost=0.00..4.01 rows=64 width=11)
                     Spawn on: All datanodes
                     ->  Seq Scan on employee t2  (cost=0.00..1.18 rows=32 width=11)
(9 rows)

gaussdb=# DROP TABLE Employee;
DROP TABLE

n_distinct取值有以下三种情况:

  • = 0:表示未知或者未计算。
  • > 0:表示去重后唯一值的个数。
  • < 0:表示其绝对值是去重后的唯一值个数占总个数的比例。

上述例子中,性别gender列的NDV值为2,代表该列只有两个值,在查询时,join行数估算结果 = 32(外表总行数)* 32(内表总行数) / 2 (内外表NDV最大值)= 512。

MCV(most common value)

MCV是指在数据库表的某一列中出现频率最高的一组值。例如,在一个销售记录表中,“商品类别”列可能会有“电子产品”、“服装”、“食品”等类别,其中“电子产品”、“服装”出现的次数最多,那么“电子产品”和“服装”就是这个列的MCV。

在查询优化过程中,MCV信息可以帮助优化器更好地估算查询结果的行数。当查询条件涉及的列有MCV信息,优化器可以根据筛选值在MCV中的频率来近似计算满足条件的行数,从而更好地判断是否应该使用索引或者全表扫描。当存在数据倾斜时,优化器会结合MCV判断是否采用倾斜优化。

MCV需满足数据平均出现频次的1.25倍。

MCV查询示例:

gaussdb=# CREATE TABLE Records (id int, Category text);
CREATE TABLE
gaussdb=# INSERT INTO Records VALUES(generate_series(1, 10), '电子产品');
INSERT 0 10
gaussdb=# INSERT INTO Records VALUES(generate_series(11, 20), '服装');
INSERT 0 10
gaussdb=# INSERT INTO Records VALUES(21, '食品'), (22, '其它');
INSERT 0 2
gaussdb=# ANALYZE Records;
ANALYZE
gaussdb=# SELECT tablename,attname,most_common_vals,most_common_freqs FROM pg_stats WHERE tablename='records';
 tablename | attname  | most_common_vals | most_common_freqs
-----------+----------+------------------+-------------------
 records   | id       |                  |
 records   | category | {服装,电子产品}  | {.454545,.454545}
(2 rows)

gaussdb=# SET enable_fast_query_shipping = off;
SET
gaussdb=# EXPLAIN SELECT * FROM Records WHERE Category  = '服装';
                          QUERY PLAN
---------------------------------------------------------------
 Streaming (type: GATHER)  (cost=0.31..1.61 rows=10 width=13)
   Node/s: All datanodes
   ->  Seq Scan on records  (cost=0.00..1.14 rows=10 width=13)
         Filter: (category = '服装'::text)
(4 rows)

gaussdb=# DROP TABLE Records;
DROP TABLE

其中most_common_vals表示MCV的值,most_common_freqs表示MCV值占总数据量的比例。

上述例子中,MCV值中“服装”的选择率为0.454545,在查询时,行数估算结果 = 22(总行数) * 0.454545(MCV值选择率)= 10。

直方图(histogram)

直方图是一种用于描述数据分布情况的工具。在数据库中,它记录了表中某一列值的分布情况,包括每个值或者值区间内数据的出现频率。例如,对于一个学生的“成绩”列,直方图可能会将成绩划分为0-59、60-69、70-79、80-89、90-100这几个区间,每个区间记录有多少学生的成绩落在这个区间内。

直方图是数据库优化器估计查询结果行数的重要依据之一。当查询条件涉及范围查询、模糊查询等情况时,优化器会参考直方图来更准确地估算行数。例如,对于查询 “SELECT * FROM students WHERE score BETWEEN 90 AND 100;”,优化器会查看直方图中90-100区间的行数来估计查询结果的大小,从而选择合适的执行计划。

直方图还可以帮助优化器在面对数据分布不均匀的列时,避免选择不合适的索引或者表连接方式。例如,如果一个列的值大部分集中在某个区间,而其他区间很少有值,优化器可以根据直方图信息选择更适合该数据分布的访问方法。

GaussDB采用等频直方图,直方图查询示例:

gaussdb=# CREATE TABLE students (nameid int,  score int);
CREATE TABLE
gaussdb=# INSERT INTO students VALUES(generate_series(1, 10), generate_series(50, 54));
INSERT 0 10
gaussdb=# INSERT INTO students VALUES(generate_series(11, 50),generate_series(60, 79));
INSERT 0 40
gaussdb=# INSERT INTO students VALUES(generate_series(51, 90),generate_series(80, 89));
INSERT 0 40
gaussdb=# INSERT INTO students VALUES(generate_series(91, 100),generate_series(91, 100));
INSERT 0 10
gaussdb=# ANALYZE students;
ANALYZE
gaussdb=# SELECT tablename,attname, histogram_bounds FROM pg_stats WHERE tablename='students';
 tablename | attname |                                                                                                                                           histogram_bounds

-----------+---------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------
 students  | nameid  | {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100}
 students  | score   | {91,92,93,94,95,96,97,98,99,100}
(2 rows)

gaussdb=# \x
Expanded display is on.
gaussdb=#  SELECT * FROM pg_stats WHERE tablename='students';
-[ RECORD 1 ]----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
schemaname             | public
tablename              | students
attname                | nameid
inherited              | f
null_frac              | 0
avg_width              | 4
n_distinct             | -1
n_dndistinct           | -1
most_common_vals       |
most_common_freqs      |
histogram_bounds       | {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100}
correlation            | .584266
most_common_elems      |
most_common_elem_freqs |
elem_count_histogram   |
partitionname          |
subpartitionname       |
-[ RECORD 2 ]----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
schemaname             | public
tablename              | students
attname                | score
inherited              | f
null_frac              | 0
avg_width              | 4
n_distinct             | -.45
n_dndistinct           | -.690476
most_common_vals       | {80,81,82,83,84,85,86,87,88,89,50,51,52,53,54,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79}
most_common_freqs      | {.04,.04,.04,.04,.04,.04,.04,.04,.04,.04,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02,.02}
histogram_bounds       | {91,92,93,94,95,96,97,98,99,100}
correlation            | .549571
most_common_elems      |
most_common_elem_freqs |
elem_count_histogram   |
partitionname          |
subpartitionname       |

gaussdb=# \x
Expanded display is off.
gaussdb=# SET enable_fast_query_shipping = off;
SET
gaussdb=# EXPLAIN SELECT * FROM students WHERE score > 90 AND score < 100;
                          QUERY PLAN
---------------------------------------------------------------
 Streaming (type: GATHER)  (cost=1.67..3.22 rows=10 width=8)
   Node/s: All datanodes
   ->  Seq Scan on students  (cost=1.36..2.75 rows=10 width=8)
         Filter: ((score > 90) AND (score < 100))
(4 rows)

gaussdb=# EXPLAIN SELECT * FROM students WHERE score > 97 AND score < 99;
                          QUERY PLAN
--------------------------------------------------------------
 Streaming (type: GATHER)  (cost=1.52..2.84 rows=2 width=8)
   Node/s: All datanodes
   ->  Seq Scan on students  (cost=1.46..2.75 rows=2 width=8)
         Filter: ((score > 97) AND (score < 99))
(4 rows)

gaussdb=# DROP TABLE students;
DROP TABLE

histogram_bounds保存直方图各桶的边界值,并按从小到大的顺序排列生成values数组。其中,values[0]到 values[1]确定第一个桶的范围,以此类推。各区间的频率大致相等,这种划分方式能够更准确地反映数据分布,并有效处理极端值和异常值。

直方图总的选择率并不是1,上述例子中由于存在MCV值,MCV的选择率之和为0.9,所以直方图总的选择率为1 - 0.9 = 0.1。在查询90-100的区间值时,行数估算结果 = 100(总行数)* 0.1(直方图总的选择率) = 10。在查询97-99的区间值时,由于97-99占了2个区间,一共9个区间,行数估算结果= 100(总行数)* 0.1 * (2/9)(查询占直方图的比例) = 2。

空值比例(nullfrac)

nullfrac描述整表的null值比例,查看方式:

gaussdb=# CREATE TABLE t1(a int, b int);
CREATE TABLE
gaussdb=# INSERT INTO t1 VALUES(generate_series(1, 10));
INSERT 0 10
gaussdb=# INSERT INTO t1 VALUES(generate_series(1, 10), generate_series(1, 10));
INSERT 0 10
gaussdb=# ANALYZE t1;
ANALYZE
gaussdb=# SELECT tablename,attname, null_frac FROM pg_stats WHERE tablename='t1';
 tablename | attname | null_frac
-----------+---------+-----------
 t1        | a       |         0
 t1        | b       |        .5
(2 rows)

gaussdb=# SET enable_fast_query_shipping = off;
SET
gaussdb=# EXPLAIN SELECT * FROM t1 WHERE b is null;
                         QUERY PLAN
-------------------------------------------------------------
 Streaming (type: GATHER)  (cost=0.31..1.57 rows=10 width=8)
   Node/s: All datanodes
   ->  Seq Scan on t1  (cost=0.00..1.10 rows=10 width=8)
         Filter: (b IS NULL)
(4 rows)
gaussdb=# DROP TABLE t1;
DROP TABLE

相关文档