我有一个 Postgres 9.2 DB,其中某个表有很多不可移动的死行:
# SELECT * FROM public.pgstattuple('mytable');
table_len | tuple_count | tuple_len | tuple_percent | dead_tuple_count | dead_tuple_len | dead_tuple_percent | free_space | free_percent
------------+-------------+-----------+---------------+------------------+----------------+--------------------+------------+--------------
2850512896 | 283439 | 100900882 | 3.54 | 2537195 | 2666909495 | 93.56 | 50480156 | 1.77
(1 row)
Run Code Online (Sandbox Code Playgroud)
正常的吸尘也显示许多不可移动的死行:
# VACUUM VERBOSE mytable;
[...]
INFO: "mytable": found 0 removable, 2404332 nonremovable row versions in 309938 out of 316307 pages
DETAIL: 2298005 dead row versions cannot be removed yet.
There were 0 unused item pointers.
0 pages are entirely empty.
CPU 1.90s/2.05u sec elapsed 16.79 sec.
[...]
Run Code Online (Sandbox Code Playgroud)
该表只有大约 300.000 个实际数据行,但有 230 万个死行(这似乎使某些查询非常慢)。
根据SELECT * FROM pg_stat_activity where xact_start is not null and datname = 'mydb' order by xact_start;
没有访问数据库的旧事务。最旧的交易有几分钟的时间,还没有修改表上的任何内容。
我还检查了select * from pg_prepared_xacts
(检查准备好的事务)和select * from pg_stat_replication
(检查挂起的复制),这两个都是空的。
在该表上执行了大量插入、更新和删除操作,因此我可以理解正在创建大量死行。但是为什么它们没有被 VACUUM 命令删除呢?
最旧的交易有几分钟的时间,还没有修改表上的任何内容。
这还不够。我认为将这些行标记为死需要的是,当这些事务开始时,没有其他事务触及这些行(对它们执行 UPDATE 或 DELETE)。
更新或删除行将保留该行以前版本的物理位置,并将其xmax
字段设置为当前事务的 TXID。从其他事务的角度来看,如果该行的旧版本是其快照的一部分,它仍然可见。每个快照都有一个xmin
和xmax
,可以比较行版本的xmin
和xmax
。关键是 VACUUM 必须将行版本与所有实时快照的组合可见性进行比较,而不是简单地检查是否确实提交了行更改。后者是必要的,但不足以回收旧版本使用的空间。
例如,这里有一系列事件,即使修改它们的事务已经完成, VACUUM 也无法清理死行:
t0
: 长时间运行的事务 TX1 开始 t0+30mn
: TX2 启动并设置为可重复读取模式。 t0+35mn
: TX1 结束。 t0+40mn
: pg_stat_activity 仅显示 10 百万旧的 TX2 t0+45mn
: VACUUM 运行但不会消除由 TX1 修改的旧版本行(因为 TX2 可能需要它们)。我能够重新创建这个。本质上,在交易内部时,
READ COMMITTED
默认事务级别:
SELECT
得到一个 AccessShareLock
VACUUM
可以清理死行版本pg_stat_activity.backend_xmin IS NULL
为交易SERIALIZABLE
或REPEATABLE READ
交易层面:
SELECT
得到一个 AccessShareLock
VACUUM
能不能清理死行版本pg_stat_activity.backend_xmin IS NOT NULL
为交易VERBOSE
将这些行报告为“不可移动行版本” 和“死行版本”CREATE TABLE bar AS
SELECT x::int FROM generate_series(1,10) AS t(x);
Run Code Online (Sandbox Code Playgroud)
作为旁注,如果您bar
在创建表后删除任何内容,这些行将变为removable
,VACUUM
您将看到。
INFO: "bar": removed # row versions in # pages
Run Code Online (Sandbox Code Playgroud)
现在,这是重新创建场景的 txn 表。
txn1 - BEGIN; SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
txn1 - SELECT * FROM bar;
txn2 - DELETE FROM bar; -- We delete after the select
txn2 - VACUUM VERBOSE bar; -- Can't remove the "dead row versions"
Run Code Online (Sandbox Code Playgroud)
VACUUM
无法删除这些行版本,因为后续的SELECT * FROM bar;
underREPEATABLE READ
仍会看到它们!在VACUUM
上述生产,
# VACUUM VERBOSE bar;
INFO: vacuuming "public.bar"
INFO: "bar": found 0 removable, 10 nonremovable row versions in 1 out of 1 pages
DETAIL: 10 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
Run Code Online (Sandbox Code Playgroud)
这正是你所看到的。
要找出阻止VACUUM
清理死行的查询,请运行此..
SELECT query, state,locktype,mode
FROM pg_locks
JOIN pg_stat_activity
USING (pid)
WHERE relation::regclass = 'bar'::regclass
AND granted IS TRUE
AND backend_xmin IS NOT NULL;
Run Code Online (Sandbox Code Playgroud)
这将返回这样的东西..
query ? state ? locktype ? mode
???????????????????????????????????????????????????????????????????????
SELECT * FROM bar; ? idle in transaction ? relation ? AccessShareLock
Run Code Online (Sandbox Code Playgroud)
所以让我们回到我们的 TXN.. 我们需要杀死/提交/回滚 txn1,然后重新运行 VACUUM
txn1 - COMMIT;
txn2 - VACUUM VERBOSE bar;
Run Code Online (Sandbox Code Playgroud)
现在我们看到,
# VACUUM VERBOSE bar;
INFO: vacuuming "public.bar"
INFO: "bar": removed 10 row versions in 1 pages
INFO: "bar": found 10 removable, 0 nonremovable row versions in 1 out of 1 pages
DETAIL: 0 dead row versions cannot be removed yet.
There were 0 unused item pointers.
Skipped 0 pages due to buffer pins.
0 pages are entirely empty.
CPU 0.00s/0.00u sec elapsed 0.00 sec.
INFO: "bar": truncated 1 to 0 pages
DETAIL: CPU 0.00s/0.00u sec elapsed 0.01 sec.
Run Code Online (Sandbox Code Playgroud)
ACCESS SHARE
表上的锁。并且,然后VACUUM
无法删除死行,因此它们被标记为“不可删除”。我认为这是非常糟糕的行为VACUUM VERBOSE
。我很想看。。
DETAIL: 10 dead row versions cannot be removed yet
could not aquire SHARE UPDATE EXCLUSIVE lock on %TABLE
Run Code Online (Sandbox Code Playgroud)VACUUM
那里。SERIALIZABLE 或 REPEATABLE READ xacts 会阻止vacuum 删除死行。它只是一个 READ COMMITTED xact,没有让真空继续进行的活动快照。其他一些东西,比如 WITH HOLD 游标、准备好的 xacts 等也可以。
对于可能会成为兔子洞之旅的问题,我的问题是有关从backend_xmin
我的工作中得到启发的更多信息
另外,感谢Daniel Vérité让我查看系统目录和VACUUM
在这个目录上的行为。
归档时间: |
|
查看次数: |
8291 次 |
最近记录: |