PostgreSQL 数据库NULL值的默认排序行为与查询、索引定义规范 - nulls first\last, asc\desc...

news/2024/11/17 19:37:48/

背景

在数据库中NULL值是指UNKNOWN的值,不存储任何值,在排序时,它排在有值的行前面还是后面通过语法来指定。

例如

-- 表示null排在有值行的前面  
select * from tbl order by id nulls first; -- 表示null排在有值行的后面 select * from tbl order by id nulls last; 

同时对于有值行,可以指定顺序排还是倒序排。

-- 表示按ID列顺序排  
select * from tbl order by id [asc]; -- 表示按ID列倒序排 select * from tbl order by id desc; 

默认的排序规则如下:

desc nulls first : null large small    asc nulls last : small large null 

当nulls [first|last]与asc|desc组合起来用时,是这样的。

值的顺序如下:

1、DEFAULT:(认为NULL比任意值都大)

desc nulls first : 顺序:null large small    asc nulls last   : 顺序:small large null 

2、NON DEFAULT: (认为NULL比任意值都小)

desc nulls last : 顺序:large small null       asc nulls first : 顺序:null small large 

由于索引是固定的,当输入排序条件时,如果排序条件与索引的排序规则不匹配时,会导致无法使用索引的实惠(顺序扫描)。导致一些不必要的麻烦。

索引定义与扫描定义不一致引发的问题

1、建表,输入测试数据

create table cc(id int not null); insert into cc select generate_series(1,1000000); 

2、建立索引(使用非默认配置,null比任意值小)

create index idx_cc on cc (id asc nulls first); 或 create index idx_cc on cc (id desc nulls last); 

3、查询,与索引定义的顺序(指NULL的相对位置)不一致时,即使使用索引,也需要重新SORT。

select * from table order by id desc nulls first limit 1; select * from table order by id [asc] nulls last limit 1; 

用到了额外的SORT

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from cc order by id limit 1;  QUERY PLAN                                                                    
---------------------------------------------------------------------------------------------------------------------------------------------  Limit  (cost=27969.43..27969.43 rows=1 width=4) (actual time=263.972..263.972 rows=1 loops=1) Output: id Buffers: shared hit=7160 -> Sort (cost=27969.43..30469.43 rows=1000000 width=4) (actual time=263.970..263.970 rows=1 loops=1) Output: id Sort Key: cc.id Sort Method: top-N heapsort Memory: 25kB Buffers: shared hit=7160 -> Bitmap Heap Scan on public.cc (cost=8544.42..22969.42 rows=1000000 width=4) (actual time=29.927..148.733 rows=1000000 loops=1) Output: id Heap Blocks: exact=4425 Buffers: shared hit=7160 -> Bitmap Index Scan on idx_cc (cost=0.00..8294.42 rows=1000000 width=0) (actual time=29.380..29.380 rows=1000000 loops=1) Buffers: shared hit=2735 Planning time: 0.098 ms Execution time: 264.009 ms (16 rows) 

3、查询,与索引定义一致(指NULL的相对位置)时,索引有效,不需要额外SORT。

select * from table order by id desc nulls last limit 1; select * from table order by id [asc] nulls first limit 1; 

不需要额外SORT

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from cc order by id nulls first limit 1;  QUERY PLAN                                                                 
---------------------------------------------------------------------------------------------------------------------------------------  Limit  (cost=0.42..0.45 rows=1 width=4) (actual time=0.014..0.014 rows=1 loops=1) Output: id Buffers: shared hit=4 -> Index Only Scan using idx_cc on public.cc (cost=0.42..22719.62 rows=1000000 width=4) (actual time=0.013..0.013 rows=1 loops=1) Output: id Heap Fetches: 1 Buffers: shared hit=4 Planning time: 0.026 ms Execution time: 0.022 ms (9 rows) 

小结

在PostgreSQL中顺序、倒序索引是通用的。不同的是null的相对位置。

因此在创建索引时,务必与业务的需求对齐,使用一致的NULL相对顺序(nulls first 或 nulls last 与asc,desc的搭配)(即NULL挨着large value还是small value),而至于值的asc, desc实际上是无所谓的。

如果业务需求的顺序与索引的顺序不一致(指null的相对顺序),那么会导致索引需要全扫,重新SORT的问题。

内核改进

1、当约束设置了not null时,应该可以不care null的相对位置,因为都没有NULL值了,优化器应该可以不管NULL的相对位置是否与业务请求的SQL的一致性,都选择非Sort模式扫描。

2、改进索引扫描方法,支持环形扫描。

参考:
https://github.com/digoal/blog/blob/master/201711/20171111_02.md

注:

  • 如果创建索引时,没有指定null的内容,但where条件部分又使用到了null的排序,那么要将asc|desc 与 last|first对应好,默认对应的操作是:
desc nulls first : null large small    asc nulls last : small large null 

在没有指定null的索引中,按照上面方法对应好即可。
下面是几个测试:

swrd=# \d ccTable "swrd.cc"Column |  Type   | Modifiers 
--------+---------+-----------id     | integer | not null Indexes: "cc_id_idx" btree (id) swrd=# explain (analyze,verbose,timing,costs,buffers) select * from cc order by id desc nulls first; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------- Index Only Scan Backward using cc_id_idx on swrd.cc (cost=0.42..30408.42 rows=1000000 width=4) (actual time=0.044..297.796 rows=1000000 loops=1) Output: id Heap Fetches: 1000000 Buffers: shared hit=7159 read=1 Planning time: 0.113 ms Execution time: 387.645 ms (6 rows) Time: 388.438 ms swrd=# explain (analyze,verbose,timing,costs,buffers) select * from cc order by id desc nulls last; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Sort (cost=127757.34..130257.34 rows=1000000 width=4) (actual time=666.996..926.348 rows=1000000 loops=1) Output: id Sort Key: cc.id DESC NULLS LAST Sort Method: external merge Disk: 13640kB Buffers: shared hit=4425, temp read=2334 written=2334 -> Seq Scan on swrd.cc (cost=0.00..14425.00 rows=1000000 width=4) (actual time=0.020..147.384 rows=1000000 loops=1) Output: id Buffers: shared hit=4425 Planning time: 0.110 ms Execution time: 1027.649 ms (10 rows)

会发现默认使用没有配置null的索引,但是在where条件中使用到了null,如果不是按照默认的对应顺序使用,则数据库会额外排序,无法使用到索引本身的排序功能。

  • 而对于在创建索引时,指定了null选项,则在where条件中和索引指定的null一致即可。

转载于:https://www.cnblogs.com/telwanggs/p/10762042.html


http://www.ppmy.cn/news/694684.html

相关文章

ANR 优化实践系列3 实例剖析集锦

概述 在前文,我们用了较多的篇幅介绍了ANR 优化实践系列1 设计原理及影响因素,并根据不同场景进行了分类,如:当前消息严重耗时,历史消息耗时严重,业务异常密集执行,进程内资源抢占,…

我的世界服务器物品属性,[娱乐|机械]Item Lore Stats —— 自定义你的物品属性[自带强化][1.10及以上]...

fileVersion: 607 #插件版本 serverVersion: 172 #服务器版本 checkForUpdates: false #是否开启自动检查更新 languageFile: language-en #语言文件 usingMcMMO: false #是否使用MCMMOusingBarAPI: true #是否使用BarAPI usingBossBarAPI: false #是否使用BossBarAPIdropCusto…

我的世界服务器物品栏mod,[娱乐|机械]Item Lore Stats —— 自定义物品[附带强化][1.7-1.10]...

# Default config for ItemLoreStats. fileVersion: 607 serverVersion: 1710 checkForUpdates: false#自动检测,true为检测最新版本更新,会一直弹出信息来让你更新,false不检测。 languageFile: language-en usingMcMMO: false usingBarAPI: false usingBossBarAPI: true dr…

Java的控制语句

流程控制语句是用来控制程序中各语句执行顺序 的语句,可以把语句组合成能完成一定功能的小逻辑模块。 控制语句分为三类:顺序、选择 和循环。“顺序结构”代表“先执行 a,再执行 b”的逻辑。 比如,先找个女朋友,再给女…

前程无忧、BOSS直聘、猎聘“抢”Z世代

配图来自Canva可画 仅用于查找职位、投递简历的互联网招聘平台已成为过去式,可精准匹配工作、学习技能,又能用来社交的互联网招聘平台越来越受欢迎。在市场需求推动下,互联网招聘平台开拓多元化服务,成为集求职、培训、社交为一体…

8000字解读蕉下:拿什么打破新消费亏钱魔咒?

主笔:邹小困、贝佳格 研究员:邹小困、贝佳格 出品:增长黑盒研究组 前言 今年4月份,专业防晒伞品牌蕉下突然向港交所提交了招股书,一时间引起不小的反响。虽说如今新品牌谋求上市已经不是什么稀奇的事,但我还…

firefly-rk3399开发板100篇 之 002 学习firefly-rk3399 wiki中gpio操作 之 打开debug口的kernel log

002 学习firefly-rk3399 wiki中gpio操作 Docs GPIO 使用 GPIO 使用 简介 GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。 RK3399 有 5 组 GPIO bank:GPIO0-GPIO4&…

firefly-rk3399开发板100篇 之 003 学习firefly-rk3399 wiki中gpio操作 之 gpio中断

003 学习firefly-rk3399 wiki中gpio操作 之 gpio中断 GPIO 使用 简介 GPIO, 全称 General-Purpose Input/Output(通用输入输出),是一种软件运行期间能够动态配置和控制的通用引脚。 RK3399 有 5 组 GPIO bank:GPIO0-GPIO4&#…