
# 单列统计信息
#### 行数和页面数
行数和页面数描述整表的统计信息，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
```
![](https://support.huaweicloud.com/distributed-devg-v10-gaussdb/public_sys-resources/note_3.0-zh-cn.png)
对于系统函数，优化器仅基于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
```
