文本列比较等于 where 子句但不选择匹配行

Nat*_*ate 6 postgresql string

我们在查询生产数据库中的表时遇到问题。一个文本列将与我们在 where 子句中过滤的字符串相等,但 postgres 不会选择该行。(我们在 postgres 11.11 上)我们的表设置如下:

(PROD)=> \d names;
                           Table "public.names"
        Column        |            Type             | Collation | Nullable | Default
----------------------+-----------------------------+-----------+----------+---------
 name                 | text                        |           | not null |
 processed_name       | text                        |           | not null |
 name_index           | integer                     |           | not null |
 when_created         | timestamp without time zone |           | not null |
Indexes:
    "names_pkey" PRIMARY KEY, btree (name, processed_name)
    "names_name_index_key" UNIQUE CONSTRAINT, btree (name_index)
    "ix_names_name" btree (name)
    "ix_names_processed_name" btree (processed_name)
Run Code Online (Sandbox Code Playgroud)

当我们处理名称列表时,我们会检查它们是否已经在表中,以防止重复添加和违反主键约束。

然而,在一个名字上,'?????? ????????? ???????',查看名称是否已经存在的查询返回一个空集,
我希望返回具有相同名称的行。但是,当我们尝试在表中插入行时,我们会遇到主键冲突

以下是一些可能更好地解释问题的查询

(PROD)=> SELECT name_index, 
    name, 
    name = '?????? ???????? ???????' names_compare_equal 
FROM names where name_index = 75128;
      name_index      |          name           | names_compare_equal
----------------------+-------------------------+---------------------
                75128 | ?????? ???????? ??????? | t
(1 row)
Run Code Online (Sandbox Code Playgroud)

但是,在名称列上进行过滤不会选择任何行。

2021-05-24 20:37:41 UTC
(PROD)=> SELECT name_index, 
    name, 
    name = '?????? ???????? ???????' 
    names_compare_equal 
FROM names 
WHERE name = '?????? ???????? ???????';

       name_index     | name | names_compare_equal
----------------------+------+---------------------
(0 rows)
Run Code Online (Sandbox Code Playgroud)

因此,如果我们尝试插入行,我们会遇到主键冲突:

(PROD)>=> INSERT INTO names (name_index, name, processed_name, when_created) 
  VALUES (89266, '?????? ???????? ???????', lower('?????? ???????? ???????'), now());

ERROR:  duplicate key value violates unique constraint "names_pkey"
DETAIL:  Key (name, processed_name)=(?????? ???????? ???????, ?????? ???????? ???????) already exists.
Run Code Online (Sandbox Code Playgroud)

更重要的是,如果我根据行的哈希查询,我会得到正确的结果:

(PROD)=> SELECT name_index, 
    name, 
    name = '?????? ???????? ???????' names_compare_equal 
FROM names 
WHERE md5(name) = md5('?????? ???????? ???????');

      name_index      |          name           | names_compare_equal
----------------------+-------------------------+---------------------
                75128 | ?????? ???????? ??????? | t
(1 row)
Run Code Online (Sandbox Code Playgroud)

这仅发生在我们的生产数据库上 - 它具有以下编码设置

      Name      |     Owner      | Encoding |   Collate   |    Ctype    |
----------------+----------------+----------+-------------+-------------+
 PROD DB        | PROD DB OWNER  | UTF8     | en_US.UTF-8 | en_US.UTF-8 |
Run Code Online (Sandbox Code Playgroud)

这对我来说很莫名其妙所以关于下一步检查什么的想法会有所帮助

Erw*_*ter 5

一个损坏的索引是这里的主要嫌疑人。测试:

SELECT * FROM names WHERE name || '' = '?????? ???????? ???????';
Run Code Online (Sandbox Code Playgroud)

该表达式name || ''不能使用任何索引,因此您将获得顺序扫描。如果该查询找到您的条目,则您的诊断是:索引损坏。可能是 just (name),但由于多个索引符合条件,请使用 重新检查EXPLAIN。(这可能不是 PK,因为它仍然会在您的测试中引发独特的违规行为,但该 PK 也可能已损坏......)

Postgres 11.11发行说明中有一条特别说明:

...请参阅下面的第二个变更日志项,它描述了在升级后重新索引索引可能是可取的情况。

或者,您的底层操作系统中的语言环境可能已更新?相同的修复:重新索引。

索引损坏还有其他原因,但唯一的另一个常见原因是硬件问题。这应该立即触发更严厉的措施,从备份开始。

重新创建受影响的索引。您可以使用REINDEX

REINDEX INDEX ix_names_name;
Run Code Online (Sandbox Code Playgroud)

如果您需要允许并发访问表,请使用非阻塞(但速度较慢)CONCURRENTLY

REINDEX INDEX ix_names_name CONCURRENTLY;
Run Code Online (Sandbox Code Playgroud)

如果有理由相信问题可能是系统性的,请重新创建表上的所有索引:

REINDEX TABLE names;
Run Code Online (Sandbox Code Playgroud)

或者整个数据库:

REINDEX DATABASE name_of_current_database;
Run Code Online (Sandbox Code Playgroud)

如果进行更大的清理,我会建议一个没有并发访问的维护窗口。以及大量的maintenance_work_mem

此外,您有两列的这些索引,name并且processed_name

"names_pkey" PRIMARY KEY, btree (name, processed_name)
"ix_names_name" btree (name)
"ix_names_processed_name" btree (processed_name)
Run Code Online (Sandbox Code Playgroud)

PK 索引(name, processed_name)可用于附加索引可以使用的所有内容(name)。仅当processed_name是一个相当大的列时,该附加索引才可能有用- 在这种情况下,我会考虑使用更有效的 PK。看: