我只是在猜测,但这是我的“锡纸帽”理论:
我同时运行了两次 Reindex(即先运行 ccnew,再运行 ccnew1),
两次都出现了错误。
也许当它失败时,并不会回滚,而是留下这些垃圾数据然后退出。
我只是在猜测,但这是我的“锡纸帽”理论:
我同时运行了两次 Reindex(即先运行 ccnew,再运行 ccnew1),
两次都出现了错误。
也许当它失败时,并不会回滚,而是留下这些垃圾数据然后退出。
这听起来非常非常非常有可能。
我也来补充一点个人看法,
Toast 索引无法通过这种方式删除,
我不得不这样做:
su postgres
psql
\connect discourse
drop index pg_toast.pg_toast_309322_index_ccnew;
drop index pg_toast.pg_toast_309322_index_ccnew1;
上述操作仅适用于 pg_toast,因为 discourse 用户没有访问该索引的权限。
PG::InsufficientPrivilege: ERROR: permission denied for schema pg_toast
删除 toast 索引非常危险。不过我想你在这里别无选择。是的,你需要通过 psql 来执行这个操作。
现在执行愉快的重新索引:
discourse=# REINDEX SCHEMA CONCURRENTLY public;
REINDEX
![]()
发现得很及时!我通过以下查询找到了所有名称中包含 “ccnew” 的索引。
psql
\connect discourse
select tablename,indexname,indexdef from pg_indexes where indexname LIKE '%ccnew%';
incoming_referer.csv (7.2 KB)
结果发现我竟然有整整 30 个这样的索引,全部位于 incoming_referers 表上。因此,我通过上述查询中的 indexdef 列验证了所有 ccnew 索引确实都是重复的。
select indexname,indexdef from pg_indexes where tablename = 'incoming_referers';
ccnew.csv (6.6 KB)
随后,我成功删除了所有这些索引。
DROP INDEX incoming_referers_pkey_ccnew;
DROP INDEX incoming_referers_pkey_ccnew_ccnew;
DROP INDEX incoming_referers_pkey_ccnew1;
...以此类推,删除全部 30 个
在那之后,我重新对整个模式(schema)进行了索引重建,但系统再次无法重建两个相同的 incoming_referers ccnew 索引,同时还发现了三个 pg_toast 索引。我删除了这些索引,然后再次对整个模式进行索引重建,结果又出现了更多错误,并在 discourse 模式中发现了更多 ccnew 索引。于是我又进行了第三次索引重建……
我无法在不报错的情况下完成完整的索引重建。系统每次都会创建新的 ccnew 索引,然后重建失败。在进行了四次完整的重建后,我删除了这些 ccnew 索引并放弃了继续尝试。我或许可以尝试非并发重建,但这会导致大量停机时间。
无论如何,我推测大多数从 PG10 升级到 PG12 并在之后尝试重新索引的用户都会遇到这些额外的 ccnew 索引,它们都应该被删除。这些索引只会占用空间,并毫无益处地增加磁盘写入 I/O。
我在 Data-Explorer 中运行了它的等价版本,管理起来稍微容易一些。
我的做法是:将所有 ccnew、ccnew1、ccnew2……ccnewn 索引全部删除,然后重新索引。这为我解决了问题。
c cnew 是 PostgreSQL 用来标记索引的一种机制。我认为,如果索引过程因某种原因失败,处理流程中存在某种低效问题,会导致这些标记被遗留下来。
我建议你先解决根本原因。我之前有重复的标签,在删除索引之前必须先将其删除。如果还剩下哪怕一个重复标签,索引就无法正确执行,并会再次创建另一个 ccnew'n' 索引并失败。
你说得对,我仍然有重复数据——无法非并发地重建非 ccnew 索引。我需要删除这些重复的行。
discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR: could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL: Key (path, incoming_domain_id)=(/search/, 3433) is duplicated.
discourse=# reindex index index_incoming_referers_on_path_and_incoming_domain_id;
ERROR: could not create unique index "index_incoming_referers_on_path_and_incoming_domain_id"
DETAIL: Key (path, incoming_domain_id)=(/search/, 1861) is duplicated.
很奇怪的是,我在 incoming_referer 表中每个 incoming_domain_id 值只看到一行。那为什么它们会被视为重复呢?
discourse=# select * from incoming_referers where path='/search/' AND incoming_domain_id IN (1861,3433);
id | path | incoming_domain_id
-------+----------+--------------------
42845 | /search/ | 1861
40763 | /search/ | 3433
(2 rows)
@sam 或 @riking,我是否应该按以下方式删除这两行:
DELETE FROM incoming_referers WHERE path='/search/' AND incoming_domain_id IN (1861,3433);
……我想我终究还是在学 PostgreSQL 了,哈哈。
不,那样会删除两个记录——你需要使用 WHERE 来查找两个 id,然后只选择其中一个 id 进行删除。
你的查询使用了损坏的索引,这就是为什么你每个只看到一个行。试试这个:
... where path LIKE '%/search/' ...
好的,这返回了 43 行。
discourse=# select * from incoming_referers where path LIKE '%/search/' ORDER BY incoming_domain_id;
id | path | incoming_domain_id
-------+-------------+--------------------
878 | /search/ | 63
33457 | /do/search/ | 567
1580 | /search/ | 602
1888 | /search/ | 663
42983 | /search/ | 1259
4896 | /search/ | 1788
42845 | /search/ | 1861
5162 | /search/ | 1861
5176 | /search/ | 1866
43350 | /search/ | 1905
17238 | /search/ | 1905
20689 | /search/ | 1982
5781 | /hg/search/ | 1987
8031 | /search/ | 2665
10325 | /search/ | 3192
11289 | /search/ | 3414
40763 | /search/ | 3433
42849 | /search/ | 3433
13087 | /search/ | 3895
13159 | /search/ | 3949
13802 | /do/search/ | 4051
14407 | /search/ | 4209
14507 | /search/ | 4211
15394 | /search/ | 4230
15533 | /search/ | 4258
45274 | /search/ | 5303
20923 | /search/ | 5400
21317 | /search/ | 5534
22928 | /search/ | 5918
22956 | /search/ | 5926
37448 | /search/ | 6393
25094 | /search/ | 6412
25594 | /search/ | 6547
39655 | /search/ | 6596
27371 | /search/ | 6986
27452 | /a/search/ | 7003
27623 | /search/ | 7041
31041 | /search/ | 7767
36943 | /search/ | 8622
37381 | /search/ | 8711
37411 | /search/ | 8716
40424 | /search/ | 9124
44451 | /search/ | 9525
(43 rows)
所以我应该运行以下命令吗?
DELETE FROM incoming_referers WHERE path LIKE '%/search/' AND id IN (42845,43350,42849);
没错,这个 DELETE 看起来是正确的,应该能让你进行干净的备份。
信不信由你,又出现了更多失败。第一组问题中,我发现 /m/search 下还有一个重复键未被 %/search/ 通配符捕获(原因不明),于是将其删除。再次重建索引(每次几乎耗时一小时!),结果又出现失败,提示 users(username_lower) 上存在重复键。
discourse=# reindex index index_users_on_username_lower;
ERROR: could not create unique index "index_users_on_username_lower"
DETAIL: Key (username_lower)=(john_smith) is duplicated.
但问题是,username_lower=john_smith 实际上只有一行记录!是时候戴上侦探帽了。
查看论坛管理界面,发现有两个独立用户,用户名分别为 “john_smith” 和 “John_Smith”——注意大小写不同。于是我直接在论坛层面删除了全小写的那个用户,因为他已有四年未活跃,之后该索引重建成功。
我又运行了一次完整重建,每次几乎耗时一小时,终于,我认为问题解决了——仅剩一个错误,但并非重复键,而是 pg_toast 中的 ccnew 索引。我将其删除了。
discourse=# REINDEX SCHEMA CONCURRENTLY public;
WARNING: cannot reindex invalid index "pg_toast.pg_toast_2783329_index_ccnew" concurrently, skipping
REINDEX
这真是一段曲折的旅程。
未来的 Discourse 升级能否/是否会自动化这些数据库修复?
如果 Peter 能够复现出一致的测试用例,PostgreSQL 索引损坏问题很可能在后续的 PostgreSQL 版本升级中得到解决。
如果并发 REINDEX 因表包含无效数据而失败,您需要执行以下操作:
修复无效数据,方法可参考 此处。
使用以下语句列出无效索引:SELECT pg_class.relname FROM pg_class, pg_index WHERE pg_index.indisvalid = false AND pg_index.indexrelid = pg_class.oid;
使用 DROP INDEX <indexname>; 删除上述列出的每个无效索引。
再次尝试执行 REINDEX。
在执行升级到 2.5.0beta5 并按照更新后指南重新索引数据库后,我遇到了以下错误:
discourse=# REINDEX SCHEMA CONCURRENTLY public;
ERROR: could not create unique index "index_plugin_store_rows_on_plugin_name_and_key_ccnew"
DETAIL: Key (plugin_name, key)=(discourse-data-explorer, q:-10) is duplicated.
我不太想在这个问题上随意尝试……那么,如何安全地删除重复项呢?
不,升级并不会损坏你的索引,它只是指出了你的索引已经损坏。你可以通过尝试将备份恢复到另一台服务器(即使是运行 pg10 的服务器)来检查这一点。或者,尝试在现有安装上重建索引。目前尚不清楚是什么导致了索引损坏,但希望 pg12 出现这种情况的可能性会降低。
升级确实有一些性能优势,但暂时不升级也不是一个坏主意。
现在已为时过晚,所以我仍在寻求关于下一步的指导。
类似这样的语句:
select id, plugin_name, key from plugin_store_rows where plugin_name like '%discourse-data-explorer%'
应该能返回相关行。我认为删除它们是安全的。
我认为需要 ilike