比较数据库性能,大家习惯看耗时、看 QPS、看执行计划。这些指标都有个共同问题:受环境影响太大。缓存命中率、后台任务、CPU 频率抖动,任何一个都能让数字漂移。
指令数(instructions)是另一条路子。它统计的是一条 SQL 在用户态真正执行了多少条 CPU 指令,跟主频和调度关系小得多,适合横向比较“同一件事,谁做的无用功更少”。
我把 openGauss、PostgreSQL、Oracle、MySQL 和 MariaDB 放在同一台机器上,用 perf stat 采了一遍。

测试环境
- 内存 251G,CPU 为 Intel Xeon E5-4650 v2 @ 2.40GHz,80 核;
- 所有数据库跑在同一台服务器上,测试期间没有其他高负载任务;
- 采样前语句已完成预热,数据完全在 Buffer Pool / Buffer Cache 中处理,排除磁盘 IO 干扰。
测试语句统一为:一张 100 万行的表,主键 id,按主键做一次等值查询 SELECT * FROM t_cpu_test WHERE id = 123456。这是最简单的索引扫描路径,能把各家的执行器开销暴露得比较干净。
Oracle 19c:先找到会话对应的 OS 进程
Oracle 是多进程架构,要先拿到当前会话对应的 SPID,再对这个进程采样。
-- 建表并插入 100 万行
CREATE TABLE t_cpu_test NOLOGGING AS
SELECT rownum AS id,
rpad('X', 50, 'X') AS info,
sysdate AS created_time
FROM dual
CONNECT BY level <= 1000000;
ALTER TABLE t_cpu_test ADD CONSTRAINT pk_t_cpu_test PRIMARY KEY (id);
BEGIN
dbms_stats.gather_table_stats(ownname => USER, tabname => 'T_CPU_TEST');
END;
/
-- 定位当前会话的 OS 进程
SELECT p.spid, s.sid, s.serial#
FROM v$session s, v$process p
WHERE s.paddr = p.addr
AND s.sid = USERENV('SID');$ sudo perf stat -e instructions:u -p 64689
^C
105,023 instructions:u
2.411828207 seconds time elapsed
openGauss 6.0.3:多线程,要找 LWTID
openGauss 是单进程多线程架构,perf 要跟的是线程号而不是进程号,通过 pg_thread_wait_status 关联。
CREATE TABLE t_cpu_test (
id integer,
info varchar(50),
created_time timestamp
);
INSERT INTO t_cpu_test
SELECT n, rpad('X', 50, 'X'), now()
FROM generate_series(1, 1000000) AS n;
ALTER TABLE t_cpu_test ADD PRIMARY KEY (id);
ANALYZE t_cpu_test;
SELECT lwtid
FROM pg_thread_wait_status
WHERE sessionid = (SELECT sessionid FROM pg_stat_activity WHERE pid = pg_backend_pid());# perf stat -e instructions:u -t 49936
^C
302,869 instructions:u
2.617229382 seconds time elapsed
PostgreSQL:9.2 与 18.1 一起测
openGauss 基于 PostgreSQL 9.2 开发,所以我把 9.2 和 18.1 都拉进来做参照,顺便看看十几年间执行器优化了多少。
PostgreSQL 取进程号最简单,一条 SELECT pg_backend_pid() 就够。建表和 openGauss 完全一致。
postgres=# SELECT pg_backend_pid();
pg_backend_pid
----------------
46606
# perf stat -e instructions:u -p 46606
^C
108,145 instructions:u
2.636957141 seconds time elapsed

MySQL 8.0 与 MariaDB 10.11
两家都是多线程 + 索引组织表,取线程号的方法一样,从 performance_schema.threads 里按连接 ID 查 THREAD_OS_ID。
CREATE TABLE t_cpu_test (
id INT PRIMARY KEY,
info VARCHAR(50),
created_time DATETIME
) ENGINE=InnoDB;
SET SESSION cte_max_recursion_depth = 1000000;
INSERT INTO t_cpu_test
WITH RECURSIVE seq AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM seq WHERE n < 1000000
)
SELECT n, RPAD('X', 50, 'X'), NOW() FROM seq;
ANALYZE TABLE t_cpu_test;
SELECT THREAD_OS_ID
FROM performance_schema.threads
WHERE PROCESSLIST_ID = CONNECTION_ID();MariaDB 有自带的 sequence 存储引擎,造数据更快,一行 seq_1_to_1000000 就够了。
INSERT INTO t_cpu_test
SELECT seq, RPAD('X', 50, 'X'), NOW()
FROM seq_1_to_1000000;# MySQL 8.0
91,101 instructions:u
3.136717427 seconds time elapsed
# MariaDB 10.11
84,393 instructions:u
3.452852099 seconds time elapsed

结果:以 PostgreSQL 18.1 为 1.00x
| 数据库版本 | 架构模式 | CPU 指令数 | 相对倍数 |
|---|---|---|---|
| MariaDB 10.11 | 多线程 | 84,393 | 0.78x |
| MySQL 8.0 | 多线程 | 91,101 | 0.84x |
| Oracle 19c | 多进程 | 105,023 | 0.97x |
| PostgreSQL 18.1 | 多进程 | 108,145 | 1.00x |
| PostgreSQL 9.2 | 多进程 | 119,349 | 1.10x |
| openGauss 6.0.3 | 多线程 | 302,869 | 2.80x |
几个值得注意的点
PostgreSQL 和 Oracle 几乎打平。 一个是开源多进程,一个是商业库,在“一次主键等值查询”这条最朴素的路径上,指令数只差 3%。这说明两者的执行器开销在同一量级,差异更多体现在优化器选择和并发模型上,而不是单条语句的机械开销。
PostgreSQL 18.1 比 9.2 少约 10% 的指令。 十几年迭代,在这样一条简单查询上还能抠出 10%,说明执行器里确实有不少细碎的开销被逐步消掉了。这个数字不大,但方向很稳定。
MySQL 和 MariaDB 最省。 两者都是索引组织表,主键就是聚簇索引,等值查询直接在 B+ 树上定位到行,比堆表多一次回表少了步骤。MariaDB 又比 MySQL 少 7% 左右,和它的执行路径更短有关。
openGauss 是 PostgreSQL 的 2.8 倍。 这个差距远超预期。它跑的是同一套 SQL、同样的数据量,多出来的指令大概率来自线程模型、内存管理和额外的安全/审计钩子。指令数不等于性能,但它确实说明这条路径上有相当多的额外工作。
指令数只能回答“谁做的无用功更少”,回答不了“谁更快”。真实业务里的排序、连接、并行、锁竞争,都不是这一个数字能覆盖的。它的价值在于:把变量控制到最小之后,给你一个不受主频和缓存干扰的横截面。
文章评论