Skip to main content

深入 PostgreSQL 源码:为执行计划添加缓存命中率显示

·473 words·3 mins
Author
GrokDb
A little bit about me

插图

简介
#

在数据库性能调优过程中,执行计划是了解查询如何执行的重要工具。为了更好地理解查询的性能表现,我们可以通过修改 PostgreSQL 的源码,为执行计划添加缓存命中率的显示。这将有助于深入分析查询性能,尤其是在使用 Memoize 节点时。

实验环境
#

  • 操作系统:Ubuntu 22.04.4 LTS
  • PostgreSQL 版本:16.4
  • 编程语言:C(PostgreSQL 源码修改)

实现步骤
#

1. 创建测试数据
#

首先,我们需要创建测试表并插入大量数据,以便生成可以分析的执行计划。

-- 删除并重新创建表
DROP TABLE IF EXISTS employees;
DROP TABLE IF EXISTS departments;

CREATE TABLE departments (
    dept_id INT PRIMARY KEY,
    dept_name VARCHAR(50)
);

CREATE TABLE employees (
    emp_id INT PRIMARY KEY,
    emp_name VARCHAR(50),
    dept_id INT,
    salary INT
);

-- 插入数据
INSERT INTO departments (dept_id, dept_name)
SELECT generate_series(1, 100), 'Department ' || generate_series(1, 100);

INSERT INTO employees (emp_id, emp_name, dept_id, salary)
SELECT generate_series(1, 200000),
       'Employee ' || generate_series(1, 200000),
       (random() * 100 + 1)::int,
       (random() * 50000 + 30000)::int;

2. 设置查询环境并获取修改前的执行计划
#

在执行计划生成之前,先进行一些查询设置,以确保使用 Memoize 节点。

SET enable_mergejoin = off;
SET enable_hashjoin = off;
SET enable_nestloop = on;
SET seq_page_cost = 2.0;
SET cpu_index_tuple_cost = 0.005;
SET work_mem = '100MB';
SET enable_memoize = on;

然后,生成查询的执行计划,以便与后续的修改结果进行对比。

EXPLAIN (ANALYZE, VERBOSE)
SELECT e.emp_name, e.salary, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary > 40000
ORDER BY e.salary DESC
LIMIT 1000;

输出结果(省略部分不相关内容):

            ->  Memoize  (cost=0.15..0.17 rows=1 width=17) 
                           Output: d.dept_name, d.dept_id
                           Cache Key: e.dept_id
                           Cache Mode: logical
                           Hits: 81005  Misses: 101  Evictions: 0  Overflows: 0  Memory Usage: 12kB
                           Worker 0:  actual time=0.000..0.000 rows=1 loops=78915
                             Hits: 78814  Misses: 101  Evictions: 0  Overflows: 0  Memory Usage: 12kB
                           ->  Index Scan using departments_pkey on public.departments d
                                 Output: d.dept_name, d.dept_id
                                 Index Cond: (d.dept_id = e.dept_id)

3. 修改 PostgreSQL 源码
#

接下来,修改 PostgreSQL 的源码以增加缓存命中率的显示。

在 VSCode 编辑器中,打开 src/backend/commands/explain.c 文件,并检索 if (mstate->stats.cache_misses > 0) 语句(位于 show_memoize_info 函数中),在此处添加以下代码:

if (mstate->stats.cache_misses > 0)
{
    char hint[128];
    double hitRate = 100.0 * mstate->stats.cache_hits / (mstate->stats.cache_hits + mstate->stats.cache_misses);

    elog(DEBUG1, "Hit Rate calculated: %.1f%%", hitRate);

    if (hitRate < 100.0)  // 检查是否小于100%,考虑到浮点数的精度问题,可以使用一个很接近100的数值
    {
        elog(DEBUG1, "Hit Rate is less than 100%%, printing optimization hint.");
        snprintf(hint, sizeof(hint), "命中率 %.1f%%.", hitRate);
        ExplainPropertyText("Optimization Hint", hint, es);
    }
    else
    {
        elog(DEBUG1, "Hit Rate is 100%%, no optimization hint needed.");
    }

    // 其他部分保持不变。
}

注意:上面代码中的 “命中率 %.1f%%.” 将会显示在执行计划中,这为优化提供了直接的建议。

4. 编译并重启 PostgreSQL
#

使用以下命令编译并安装修改后的 PostgreSQL:

./configure --prefix=$HOME/pgsql --enable-debug --enable-cassert CFLAGS="-O0 -g"
make -j$(nproc)
make install
$HOME/pgsql/bin/pg_ctl -D $HOME/pgsql/data restart

5. 设置查询环境并查看修改后的执行计划
#

在每次执行查询前,先执行以下设置以确保环境一致性:

SET enable_mergejoin = off;
SET enable_hashjoin = off;
SET enable_nestloop = on;
SET seq_page_cost = 2.0;
SET cpu_index_tuple_cost = 0.005;
SET work_mem = '100MB';
SET enable_memoize = on;

然后,执行与之前相同的查询语句,观察执行计划中是否显示了缓存命中率。

EXPLAIN (ANALYZE, VERBOSE)
SELECT e.emp_name, e.salary, d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary > 40000
ORDER BY e.salary DESC
LIMIT 1000;

输出结果(省略部分不相关内容):

            ->  Memoize  (cost=0.15..0.17 rows=1 width=17)
                           Output: d.dept_name, d.dept_id
                           Cache Key: e.dept_id
                           Cache Mode: logical
                           Optimization Hint: 命中率 99.9%.
                           Hits: 81120  Misses: 101  Evictions: 0  Overflows: 0  Memory Usage: 12kB
                           Worker 0:  actual time=0.000..0.000 rows=1 loops=78800
                             Hits: 78699  Misses: 101  Evictions: 0  Overflows: 0  Memory Usage: 12kB
                           ->  Index Scan using departments_pkey on public.departments d 
                                Output: d.dept_name, d.dept_id
                                Index Cond: (d.dept_id = e.dept_id)

在这个执行计划中,Optimization Hint: 命中率 99.9%. 清晰地显示在执行计划中。

6. 总结
#

通过这次实验,我们成功地在 PostgreSQL 的执行计划中添加了缓存命中率的显示。这为我们在调优数据库查询性能时提供了更多的信息,帮助我们更好地理解查询的行为。

参考资料
#

注意:本实验是在受控的开发环境中进行的。在对生产系统应用任何更改之前,请务必备份数据并进行彻底测试。