1、摘要
在数据库兼容性开发过程中,NULL 的排序规则是一个容易被忽视但非常关键的问题。当海山PG在实现 MySQL 兼容时,如果不处理 NULL 排序行为,很多 SQL 的执行结果会与 MySQL 不一致,从而导致应用兼容性问题。
本文从行为差异、实现原理、海山PG的内核机制以及索引排序关系等多个角度,系统分析 海山PG与 MySQL 在 NULL 排序方面的差异,并给出在实现 MySQL 兼容数据库时的工程实践方案。
2、NULL 的基本语义
在SQL标准中,NULL表示“未知值(unknown)”,而不是0或空字符串。
NULL具有两个重要特性:
1)NULL不参与普通比较
NULL = NULL → NULL
NULL < 1 → NULL
2)NULL只能通过IS NULL或IS NOT NULL判断
col IS NULL
col IS NOT NULL
SQL标准并没有严格规定NULL在排序中的默认位置,因此不同数据库实现可能存在差异。
3、NULL排序差异
假设有如下测试表:
CREATE TABLE t(id int);
INSERT INTO t VALUES (1),(2),(NULL),(3);
3.1 MySQL默认排序行为
执行以下SQL:
SELECT * FROM t ORDER BY id ASC;
结果:
NULL
1
2
3
执行以下SQL:
SELECT * FROM t ORDER BY id DESC;
结果:
3
2
1
NULL
结论:
MySQL规则
ASC → NULL FIRST
DESC → NULL LAST
MySQL实际上将NULL视为最小值。
3.2 海山PG默认排序行为
执行以下SQL:
SELECT * FROM t ORDER BY id ASC;
结果:
1
2
3
NULL
执行以下SQL:
SELECT * FROM t ORDER BY id DESC;
结果:
NULL
3
2
1
海山PG规则:
ASC → NULL LAST
DESC → NULL FIRST
海山PG将NULL视为最大值。
4、SQL标准中的NULL排序控制
SQL标准允许显式指定NULL排序位置:
ORDER BY column NULLS FIRST
ORDER BY column NULLS LAST
例如:
SELECT * FROM t ORDER BY id ASC NULLS FIRST;
SELECT * FROM t ORDER BY id DESC NULLS LAST;
海山PG完整支持该语法,而MySQL不支持NULLS FIRST / LAST语法。
5、海山PG与MySQL不同的原因
两者差异主要来自数据库内部比较逻辑的不同。
MySQL比较逻辑:
NULL < ANY NON-NULL VALUE
因此:
ASC → NULL FIRST
DESC → NULL LAST
海山PG比较逻辑:
NULL > ANY NON-NULL VALUE
因此:
ASC → NULL LAST
DESC → NULL FIRST
也就是说,MySQL将NULL视为“无限小”;海山PG将NULL视为“无限大”。
6、海山PG中ORDER BY排序实现
海山PG中ORDER BY排序主要由tuplesort模块完成。
执行流程:
Executor
↓
ExecSort
↓
tuplesort_begin_xxx()
↓
tuplesort_puttupleslot()
↓
tuplesort_performsort()
排序过程中真正决定排序顺序的是SortSupport comparator。
海山PG使用SortSupport结构控制排序行为:
typedef struct SortSupportData
{
bool ssup_reverse;
bool ssup_nulls_first;
int (*comparator) (Datum x, Datum y, SortSupport ssup);
} SortSupportData;
NULL比较逻辑示意:
if (isNull1 && isNull2)
return 0;
if (isNull1)
return ssup_nulls_first ? -1 : 1;
if (isNull2)
return ssup_nulls_first ? 1 : -1;
真正控制NULL位置的是ssup_nulls_first参数。
7、海山PG默认NULL排序规则
当SQL未指定NULLS FIRST / LAST时,海山PG会自动推导:
ASC → NULLS LAST
DESC → NULLS FIRST
因此:
ORDER BY col ASC
等价于
ORDER BY col ASC NULLS LAST
ORDER BY col DESC
等价于
ORDER BY col DESC NULLS FIRST
8、索引扫描与NULL排序的关系
NULL排序不仅影响结果顺序,还影响是否能够使用索引避免排序。
示例:
CREATE INDEX idx_age ON user(age);
EXPLAIN
SELECT * FROM user ORDER BY age;
如果排序顺序与索引一致,执行计划可能为:
Index Scan using idx_age
否则可能需要:
Seq Scan
Sort
海山PG的BTree 索引规定:
NULLS ARE LARGER THAN ANY VALUE
因此:
ASC → NULL LAST
DESC → NULL FIRST
这与海山PG的默认排序规则保持一致,从而能够直接利用索引顺序。
假设为了兼容 MySQL,将海山PG修改为:
NULL < ANY VALUE
那么排序规则会变为:
ASC → NULL FIRST
DESC → NULL LAST
此时 BTree 索引顺序将与排序规则不一致,很多 ORDER BY 查询将无法直接利用索引,必须额外执行 Sort 操作,导致性能下降。
9、总结
海山PG与MySQL在NULL排序行为上的差异源于其内部比较逻辑不同:
MySQL:NULL < ANY VALUE
海山PG:NULL > ANY VALUE
海山PG的设计使默认排序规则与BTree索引顺序一致,在海山PG中,BTree索引同样将NULL视为大于任何非NULL值,因此:
(1)ORDER BY col ASC 可以直接利用ASC索引顺序扫描
(2)ORDER BY col DESC 可以直接利用反向索引扫描
这种一致性使得很多ORDER BY查询能够直接通过Index Scan返回有序结果,从而避免额外的Sort操作,提高查询效率。
因此,在实现MySQL兼容数据库时,需要同时考虑两个方面:
(1)排序结果兼容性
MySQL默认行为为
ASC → NULL FIRST
DESC → NULL LAST
(2)索引使用与执行计划稳定性
如果简单修改海山PG内核,使其NULL比较逻辑变为NULL < ANY VALUE,虽然可以获得与MySQL一致的排序结果,但会导致BTree 索引顺序与排序规则不一致,从而使很多原本可以利用索引的ORDER BY查询退化为:
Index Scan
Sort
或
Seq Scan
Sort
这将带来明显的性能下降。






