PostgreSQL citext如何存储在b树索引中?小写字母还是小写字母?

时间:2019-04-10 07:03:16

标签: postgresql query-performance case-insensitive b-tree-index

我在citext中将PostgreSQL用于所有文本列类型。我想知道citext的效果。

我对具有b树索引的文本列执行了简单的WHERE语句基准,但是在查询成本方面我看不到任何差异。

例如:

Select * From table_text where a = '1';

Select * From table_citext where a= '1';

这些查询的查询费用相同。

据我了解,citext按原样存储字符串,但不将其转换为小写。因此,当在WHERE子句中使用一个值时,它会在lower函数中使用b树索引的每个节点中的每个比较(我使用b树索引)。

如果这就是我所说的,应该会导致性能问题,但事实并非如此。

PostgreSQL如何做到这一点?
PostgreSQL如何在b树索引中存储citext列值?

1 个答案:

答案 0 :(得分:1)

citext是按输入形式存储的,不进行任何小写转换。这也适用于作为b树索引键进行存储。

神奇之处在于citext的比较功能:

/*
 * citextcmp()
 * Internal comparison function for citext strings.
 * Returns int32 negative, zero, or positive.
 */
static int32
citextcmp(text *left, text *right, Oid collid)
{
    char       *lcstr,
               *rcstr;
    int32       result;

    /*
     * We must do our str_tolower calls with DEFAULT_COLLATION_OID, not the
     * input collation as you might expect.  This is so that the behavior of
     * citext's equality and hashing functions is not collation-dependent.  We
     * should change this once the core infrastructure is able to cope with
     * collation-dependent equality and hashing functions.
     */

    lcstr = str_tolower(VARDATA_ANY(left), VARSIZE_ANY_EXHDR(left), DEFAULT_COLLATION_OID);
    rcstr = str_tolower(VARDATA_ANY(right), VARSIZE_ANY_EXHDR(right), DEFAULT_COLLATION_OID);

    result = varstr_cmp(lcstr, strlen(lcstr),
                        rcstr, strlen(rcstr),
                        collid);

    pfree(lcstr);
    pfree(rcstr);

    return result;
}

是的,这会产生一些开销。它的昂贵程度还取决于数据库的默认排序规则。

我将使用没有索引的查询来演示这一点。我正在使用德语排序规则:

SHOW lc_collate;
 lc_collate 
------------
 de_DE.utf8
(1 row)

首先使用text

CREATE TABLE large_text(t text NOT NULL);

INSERT INTO large_text
   SELECT i||'text'
   FROM generate_series(1, 1000000) AS i;

VACUUM (FREEZE, ANALYZE) large_text;

\timing on

SELECT * FROM large_text WHERE t = TEXT 'mama';
 t 
---
(0 rows)

Time: 79.862 ms

现在使用citext进行相同的实验:

CREATE TABLE large_citext(t citext NOT NULL);

INSERT INTO large_citext
   SELECT i||'text'
   FROM generate_series(1, 1000000) AS i;

VACUUM (FREEZE, ANALYZE) large_citext;

\timing on

SELECT * FROM large_citext WHERE t = CITEXT 'mama';
 t 
---
(0 rows)

Time: 567.739 ms

所以citext慢大约七倍。

但是请不要忘记,这些实验中的每一个都执行了具有百万次比较的顺序扫描。
如果使用索引,差异将不会明显:

CREATE INDEX ON large_text (t);

Time: 5443.993 ms (00:05.444)

SELECT * FROM large_text WHERE t = CITEXT 'mama';
 t 
---
(0 rows)

Time: 1.867 ms


CREATE INDEX ON large_citext (t);

Time: 28009.904 ms (00:28.010)

SELECT * FROM large_citext WHERE t = CITEXT 'mama';
 t 
---
(0 rows)

Time: 1.988 ms

您看到CREATE INDEX花费了citext列更长的时间(它必须执行很多比较),但是查询花费的时间大约相同。

原因是,如果使用索引扫描,则只需要进行很少的比较:对于要访问的2-3个索引块中的每一个,您都将执行二进制搜索,并且可能必须重新检查表中的表行。位图索引扫描的情况。