昨天看到微信群中,有人提问:pg对于标记为删除的页,是否会扫描到?

今天做了一下测试,发现如果删除的是表的最后连续的几个页(根据ctid来确定数据插入先后,只讨论有insert的情况)中的数据,最后几个页经过vacuum后,会被释放回操作系统,自然不会被扫描到,但如果不是最后的页会不会扫描到,下面测试一下。

1. 测试无索引的情况:

swrd=# \d t1
Table "swrd.t1"
Column | Type | Collation | Nullable | Default
--------+-------------------+-----------+----------+---------
c1 | integer | | |
c2 | character varying | | |
c3 | character varying | | |
c4 | character varying | | |
swrd=# insert into t1 select i,md5(i::text),md5(i::text),md5(i::text) from generate_series(1,10000) t(i);
INSERT 0 10000
swrd=# select max(ctid) from t1;
max
----------
(40,80)
(1 row)

在t1中插入1w条记录,t1表是一共占用了41个页,查看记录,一个块中存放248条记录。

##全表扫描,可以看到buffers数是41个。
swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 ;
QUERY PLAN
-------------------------------------------------------------------------------------------------------------
Seq Scan on swrd.t1 (cost=0.00..141.00 rows=10000 width=103) (actual time=0.020..1.696 rows=10000 loops=1)
Output: c1, c2, c3, c4
Buffers: shared hit=41
Planning Time: 0.142 ms
Execution Time: 2.650 ms
(5 rows) ##删除几个页,看是否有变化,不做vacuum
##删除第13页到16页
swrd=# alter table t1 set(autovacuum_enabled=off);
ALTER TABLE
swrd=# delete from t1 where c1>=2977 and c1<=4216;
DELETE 1240
##仍然读取的buffer个数是41个,因为页还没做清理,所以很正常,下面做一下vacuum
swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 ;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Seq Scan on swrd.t1 (cost=0.00..128.60 rows=8760 width=103) (actual time=0.066..2.961 rows=8760 loops=1)
Output: c1, c2, c3, c4
Buffers: shared read=41
Planning Time: 0.694 ms
Execution Time: 3.836 ms
(5 rows)
##做一下vacuum
swrd=# vacuum VERBOSE t1;
INFO: vacuuming "swrd.t1"
INFO: "t1": removed 0 row versions in 5 pages
INFO: "t1": found 0 removable, 8760 nonremovable row versions in 41 out of 41 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 46060313
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO: vacuuming "pg_toast.pg_toast_37066"
INFO: index "pg_toast_37066_index" now contains 0 row versions in 1 pages
DETAIL: 0 index row versions were removed.
0 index pages have been deleted, 0 are currently reusable.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
INFO: "pg_toast_37066": found 0 removable, 0 nonremovable row versions in 0 out of 0 pages
DETAIL: 0 dead row versions cannot be removed yet, oldest xmin: 46060313
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins, 0 frozen pages.
0 pages are entirely empty.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
VACUUM
swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 ;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Seq Scan on swrd.t1 (cost=0.00..128.60 rows=8760 width=103) (actual time=0.011..1.162 rows=8760 loops=1)
Output: c1, c2, c3, c4
Buffers: shared hit=41
Planning Time: 0.092 ms
Execution Time: 1.972 ms
(5 rows)

扫描的page仍然是41个。在做vacuum full后,扫描的页数才减少了5个。

swrd=# vacuum FULL t1;
VACUUM
swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 ;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------
Seq Scan on swrd.t1 (cost=0.00..123.60 rows=8760 width=103) (actual time=0.057..2.040 rows=8760 loops=1)
Output: c1, c2, c3, c4
Buffers: shared read=36
Planning Time: 0.089 ms
Execution Time: 2.879 ms
(5 rows)

2. 测试使用索引的情况:

swrd=# create index on t1(c1);
CREATE INDEX
swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 where c1>=2977 and c1<=4216 ;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Index Scan using t1_c1_idx on swrd.t1 (cost=0.29..42.08 rows=1240 width=103) (actual time=0.098..0.415 rows=1240 loops=1)
Output: c1, c2, c3, c4
Index Cond: ((t1.c1 >= 2977) AND (t1.c1 <= 4216))
Buffers: shared hit=7
Planning Time: 0.143 ms
Execution Time: 0.558 ms
(6 rows)

c1值在2977-4216共占用了5个页,其他两个页是来自索引的。

使用插件pageinspect看一下索引的结构:

swrd=# select * from bt_metap('t1_c1_idx');
magic | version | root | level | fastroot | fastlevel | oldest_xact | last_cleanup_num_tuples
--------+---------+------+-------+----------+-----------+-------------+-------------------------
340322 | 3 | 3 | 1 | 3 | 1 | 0 | -1
(1 row)

看到索引的level是1,也就是该索引是2级结构,包含了meta page,root page,leaf page。另外读取的buffer是root page和leaf page。

下面删掉其中的两个页的数据量:

swrd=# delete from t1 where c1 >=3225 and c1<=3720;
DELETE 496
swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 where c1>=2977 and c1<=4216
swrd-# ;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Index Scan using t1_c1_idx on swrd.t1 (cost=0.29..42.08 rows=1240 width=103) (actual time=0.086..0.428 rows=744 loops=1)
Output: c1, c2, c3, c4
Index Cond: ((t1.c1 >= 2977) AND (t1.c1 <= 4216))
Buffers: shared hit=8 dirtied=1
Planning Time: 0.141 ms
Execution Time: 0.528 ms
(6 rows) swrd=# explain (ANALYZE,VERBOSE,COSTS,BUFFERS,TIMING) select * from t1 where c1>=2977 and c1<=4216
;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
Index Scan using t1_c1_idx on swrd.t1 (cost=0.29..42.08 rows=1240 width=103) (actual time=0.064..0.253 rows=744 loops=1)
Output: c1, c2, c3, c4
Index Cond: ((t1.c1 >= 2977) AND (t1.c1 <= 4216))
Buffers: shared hit=5
Planning Time: 0.125 ms
Execution Time: 0.348 ms
(6 rows)

通过使用bt_page_items查看索引中的数据,一个索引页中包含了1473条记录,并可以看到删除的页均在一个leaf page上面。

由于我们之前避免vacuum的干扰,将它关闭了,手动执行一下,可以看到对应的索引记录也被删除了。此处删除的数据是(13,1)到(14,248)的记录。

swrd=# select * from bt_page_items('t1_c1_idx',4);
itemoffset | ctid | itemlen | nulls | vars | data
------------+----------+---------+-------+------+-------------------------
1 | (17,201) | 16 | f | f | 41 11 00 00 00 00 00 00
2 | (11,217) | 16 | f | f | 81 0b 00 00 00 00 00 00
3 | (11,218) | 16 | f | f | 82 0b 00 00 00 00 00 00
4 | (11,219) | 16 | f | f | 83 0b 00 00 00 00 00 00
5 | (11,220) | 16 | f | f | 84 0b 00 00 00 00 00 00
6 | (11,221) | 16 | f | f | 85 0b 00 00 00 00 00 00
7 | (11,222) | 16 | f | f | 86 0b 00 00 00 00 00 00
8 | (11,223) | 16 | f | f | 87 0b 00 00 00 00 00 00
9 | (11,224) | 16 | f | f | 88 0b 00 00 00 00 00 00
10 | (11,225) | 16 | f | f | 89 0b 00 00 00 00 00 00
11 | (11,226) | 16 | f | f | 8a 0b 00 00 00 00 00 00
12 | (11,227) | 16 | f | f | 8b 0b 00 00 00 00 00 00
13 | (11,228) | 16 | f | f | 8c 0b 00 00 00 00 00 00
14 | (11,229) | 16 | f | f | 8d 0b 00 00 00 00 00 00
15 | (11,230) | 16 | f | f | 8e 0b 00 00 00 00 00 00
16 | (11,231) | 16 | f | f | 8f 0b 00 00 00 00 00 00
17 | (11,232) | 16 | f | f | 90 0b 00 00 00 00 00 00
18 | (11,233) | 16 | f | f | 91 0b 00 00 00 00 00 00
19 | (11,234) | 16 | f | f | 92 0b 00 00 00 00 00 00
20 | (11,235) | 16 | f | f | 93 0b 00 00 00 00 00 00
.....
277 | (12,244) | 16 | f | f | 94 0c 00 00 00 00 00 00
278 | (12,245) | 16 | f | f | 95 0c 00 00 00 00 00 00
279 | (12,246) | 16 | f | f | 96 0c 00 00 00 00 00 00
280 | (12,247) | 16 | f | f | 97 0c 00 00 00 00 00 00
281 | (12,248) | 16 | f | f | 98 0c 00 00 00 00 00 00
282 | (15,1) | 16 | f | f | 89 0e 00 00 00 00 00 00
283 | (15,2) | 16 | f | f | 8a 0e 00 00 00 00 00 00
284 | (15,3) | 16 | f | f | 8b 0e 00 00 00 00 00 00
285 | (15,4) | 16 | f | f | 8c 0e 00 00 00 00 00 00
....
769 | (16,240) | 16 | f | f | 70 10 00 00 00 00 00 00
770 | (16,241) | 16 | f | f | 71 10 00 00 00 00 00 00
771 | (16,242) | 16 | f | f | 72 10 00 00 00 00 00 00
772 | (16,243) | 16 | f | f | 73 10 00 00 00 00 00 00
773 | (16,244) | 16 | f | f | 74 10 00 00 00 00 00 00
774 | (16,245) | 16 | f | f | 75 10 00 00 00 00 00 00
775 | (16,246) | 16 | f | f | 76 10 00 00 00 00 00 00
776 | (16,247) | 16 | f | f | 77 10 00 00 00 00 00 00
777 | (16,248) | 16 | f | f | 78 10 00 00 00 00 00 00
778 | (17,1) | 16 | f | f | 79 10 00 00 00 00 00 00
....

看到上面的执行计划,第一次执行时是有8个buffer,其中一个是脏页,这里后面再深入了解一下,再补充上,第二次时,读取的buffer数就变成了5个,我们删除的两个页已经不再了。也就是使用索引扫描时没有扫描到删除的页,这个也很好理解,因为索引是根据ctid来扫描的,删除的页不存在要查询的数据了,自然就不会在扫了。

总结:

  1. 全表扫描时,读取数据时可能会扫描到删除的页面。(按理说在读数据时,只需要读取buftag对应的页就可以了,但测试发现膨胀产生的脏页仍会被读到。很疑惑。)
  2. 索引扫描时,不会扫描到删除的页面,但对于有hot操作的记录,仍需要使用原记录的line pointer。

pg中删除的页是否仍被访问的更多相关文章

  1. 数据库中删除语句Drop、Delete、Truncate的相同点和不同点的比较

    数据库删除语句的分别介绍: Delete:用于删除表中的行(注:可以删除某一行:也可以在不删除表的情况下(即意味着表的结构.属性.索引完整)删除所有行) 语法:删除某一行:Delete From 表名 ...

  2. word页码上加横线&&word删除单页页眉

    word(2010)页码上加横线 插入——>页脚(选择年刊型)——>如图 然后拖住“竖条条”将页码拖到正中间——>点中页脚右击——>选中“表格属性”——>“边框和底纹”— ...

  3. 高端内存映射之vmalloc分配内存中不连续的页--Linux内存管理(十九)

    1 内存中不连续的页的分配 根据上文的讲述, 我们知道物理上连续的映射对内核是最好的, 但并不总能成功地使用. 在分配一大块内存时, 可能竭尽全力也无法找到连续的内存块. 在用户空间中这不是问题,因为 ...

  4. 根据 Power BI Desktop(预览版)中的报表页创建工具提示

    根据 Power BI Desktop 中创建的报表页,可创建直观丰富的报表工具提示,这些提示在你将鼠标悬停在视觉对象上时显示. 通过创建用作工具提示的报表页,使自定义工具提示包含视觉对象.图像以及在 ...

  5. 【技巧】easyUI的datagrid,如何在翻页以后仍能记录被选中的行

    easyUI的datagrid在复选框多选时,如何在翻页以后仍能记录被选中的行: 注意datagrid中需要配置idField属性,一般为数据的主键

  6. Expo大作战(十五)--expo中splash启动页的详细机制

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  7. jQuery easyUI的datagrid,如何在翻页以后仍能记录被选中的行

    1.先给出问题解决后的代码 <%@ page language="java" import="java.util.*" pageEncoding=&quo ...

  8. jquery easyui datagrid 在翻页以后仍能记录被选中的行及刷新设置选中行数据

    //easyUI的datagrid在复选框多选时,如何在翻页以后仍能记录被选中的行://注意datagrid中需要配置idField属性,一般为数据的主键 $.ajax({ type: 'GET', ...

  9. MySQL_(Java)使用JDBC向数据库中删除(delete)数据

    MySQL_(Java)使用JDBC向数据库发起查询请求 传送门 MySQL_(Java)使用JDBC向数据库中插入(insert)数据 传送门 MySQL_(Java)使用JDBC向数据库中删除(d ...

随机推荐

  1. 从汉诺塔游戏理解python递归函数

    汉诺塔游戏规则: 有三根相邻的柱子,标号为A,B,C,A柱子上从下到上按金字塔状叠放着n个不同大小的圆盘,现在把所有盘子一个一个移动到柱子B上,并且每次移动同一根柱子上都不能出现大盘子在小盘子上方 图 ...

  2. eclipse自动生成uml

    见如下链接: https://blog.csdn.net/zyf_balance/article/details/44937197 若eclipse无法生成,可以安装myeclipse使用自带的方法: ...

  3. vim相关命令单独记载

    1. 无敌的可扩展性 1.1 可扩展性给了软件强大的生命 曾几何时,Windows用户对软件的可扩展性没有概念,他们只能对他们使用的软件进行非常有限的定制.扩展软件的权利保留在软件开发者手中.软件的使 ...

  4. 第二阶段Sprint4

    昨天:事实现保存到指定路径,并能够选择播放 今天:放弃改文件名,自动生成,实现暂停后继续录制 遇到的问题:不知道为什么实现不了,不然之前录制的视频就会丢失重新录

  5. Alpha 冲刺报告(4/10)

    Alpha 冲刺报告(4/10) 队名:洛基小队 峻雄(组长) 已完成:继续行动脚本的编写 明日计划:尽量完成角色的移动 剩余任务:物品背包交互代码 困难:具体编码进展比较缓慢 ----------- ...

  6. .NET 类库研究必备参考 扣丁格鲁

    .NET 类库的强大让我们很轻松的解决常见问题,作为一个好专研的程序员,为了更上一层楼,研究CLR的基础类库实现是快速稳定的捷径. 一般场景下,采用 Reflector可以反射出.NET 的部分实现出 ...

  7. PHP 中各种命名规则的总结

    一般约定而言 类.函数和变量的名字应该是能够让代码阅读者能够容易地知道这些代码的作用,应该避免使用凌磨两可的命名. 以下是在开发过程中常见的命名规则总结 1.类的命名 使用大写字母作为词的分割,其余的 ...

  8. Vue 小组件input keyup.enter绑定

    <div id="todo-list-example"> <input v-model="newTodoText" v-on:keyup.en ...

  9. 最小费用流spfa算法模板(pascal)

    以前写过,现在的码风与以前有些变化,主要是用数组模拟邻接表存图,以前是用指针存图. 以前的博文:http://www.cnblogs.com/Currier/p/6387732.html 洛谷可评测. ...

  10. 前端基础:HTTP 状态码详解

    HTTP 状态码详解 1xx(信息类):表示接收到请求并继续处理 100 客户端应当继续发送请求.这个临时响应是用来通知客户端他的部分请求已经被服务器接收,且仍未被拒绝.客户端应当继续发送请求的剩余部 ...