使用串联和 LIKE 查询性能
Query performance with concatenation and LIKE
有人可以解释这 3 个查询之间的性能差异吗?
concat()
函数:
explain analyze
select * from person
where (concat(last_name, ' ', first_name, ' ', middle_name) like '%Ива%');
Seq Scan on person (cost=0.00..4.86 rows=1 width=15293) (actual time=0.032..0.140 rows=6 loops=1)
Filter: (pg_catalog.concat(last_name, ' ', first_name, ' ', middle_name) ~~ '%Ива%'::text)
Total runtime: 0.178 ms
SQL 与 ||
:
的标准连接
explain analyze
select * from person
where ((last_name || ' ' || first_name || ' ' || middle_name) like '%Ива%');
Seq Scan on person (cost=0.00..5.28 rows=1 width=15293) (actual time=0.023..0.080 rows=6 loops=1)
Filter: ((((((last_name)::text || ' '::text) || (first_name)::text) || ' '::text) || (middle_name)::text) ~~ '%Ива%'::text)
Total runtime: 0.121 ms
单独搜索字段:
explain analyze
select * from person
where (last_name like '%Ива%') or (first_name like '%Ива%') or (middle_name like '%Ива%');
Seq Scan on person (cost=0.00..5.00 rows=1 width=15293) (actual time=0.018..0.060 rows=6 loops=1)
Filter: (((last_name)::text ~~ '%Ива%'::text) OR ((first_name)::text ~~ '%Ива%'::text) OR ((middle_name)::text ~~ '%Ива%'::text))
Total runtime: 0.097 ms
为什么 concat()
最慢,为什么有几个 like
条件更快?
此查询在每一行上调用函数都有开销
explain analyze
select * from person
where (concat(last_name, ' ', first_name, ' ', middle_name) like '%Ива%');
此查询速度更快,因为没有执行其他操作
explain analyze
select * from person
where (last_name like '%Ива%') or (first_name like '%Ива%') or (middle_name like '%Ива%');
虽然不是具体的答案,但以下内容可能会帮助您得出一些结论:
调用 concat
连接三个字符串,或使用 ||
运算符,导致 postgres 必须分配一个新缓冲区来保存连接的字符串,然后复制字符放入其中。这必须为每一行完成。然后必须在最后释放缓冲区。
在将三个条件组合在一起的情况下,postgres 可能只需要评估其中的一个或两个就可以决定是否必须包含该行。
与对 concat
的函数调用相比,使用 ||
运算符的表达式求值可能更有效,或者可能更容易优化。如果发现内部运算符有一些特殊情况处理,我不会感到惊讶。
如评论所述,您的样本太小,无论如何都无法得出正确的结论。在几分之一毫秒的水平上,其他噪声因素可能会扭曲结果。
到目前为止您所观察到的内容很有趣,但并不重要。连接字符串的开销很小。
这些表达式之间更重要的区别不会显示在没有索引的最小测试用例中。
前两个例子不是sargable(除非你建立一个定制的表达式索引):
where concat(last_name, ' ', first_name, ' ', middle_name) like '%Ива%'
where (last_name || ' ' || first_name || ' ' || middle_name) like '%Ива%'
虽然这个是:
where last_name like '%Ива%' or first_name like '%Ива%' or middle_name like '%Ива%'
也就是说,它可以使用普通的三元组索引来产生很好的效果(列的顺序在 GIN 索引中并不重要):
CREATE INDEX some_idx ON person USING gin (first_name gin_trgm_ops
, middle_name gin_trgm_ops
, last_name gin_trgm_ops);
说明:
- PostgreSQL LIKE query performance variations
如果可能为 NULL,则测试不正确
concat()
通常比使用 ||
的简单字符串连接稍微贵一些。它也是 不同的 :如果任何输入字符串为 NULL,则在您的第二种情况下连接的结果也为 NULL,但在您的第一种情况下不是,因为 concat()
只是忽略NULL 值 - 但您仍然会在结果中得到一个无用的 space 字符。
详细解释:
- Combine two columns and add into one new column
如果您正在寻找干净、优雅的表达方式(成本大致相同),请改用 concat_ws()
:
concat_ws( ' ', last_name, first_name, middle_name)
有人可以解释这 3 个查询之间的性能差异吗?
concat()
函数:
explain analyze
select * from person
where (concat(last_name, ' ', first_name, ' ', middle_name) like '%Ива%');
Seq Scan on person (cost=0.00..4.86 rows=1 width=15293) (actual time=0.032..0.140 rows=6 loops=1)
Filter: (pg_catalog.concat(last_name, ' ', first_name, ' ', middle_name) ~~ '%Ива%'::text)
Total runtime: 0.178 ms
SQL 与 ||
:
explain analyze
select * from person
where ((last_name || ' ' || first_name || ' ' || middle_name) like '%Ива%');
Seq Scan on person (cost=0.00..5.28 rows=1 width=15293) (actual time=0.023..0.080 rows=6 loops=1)
Filter: ((((((last_name)::text || ' '::text) || (first_name)::text) || ' '::text) || (middle_name)::text) ~~ '%Ива%'::text)
Total runtime: 0.121 ms
单独搜索字段:
explain analyze
select * from person
where (last_name like '%Ива%') or (first_name like '%Ива%') or (middle_name like '%Ива%');
Seq Scan on person (cost=0.00..5.00 rows=1 width=15293) (actual time=0.018..0.060 rows=6 loops=1)
Filter: (((last_name)::text ~~ '%Ива%'::text) OR ((first_name)::text ~~ '%Ива%'::text) OR ((middle_name)::text ~~ '%Ива%'::text))
Total runtime: 0.097 ms
为什么 concat()
最慢,为什么有几个 like
条件更快?
此查询在每一行上调用函数都有开销
explain analyze
select * from person
where (concat(last_name, ' ', first_name, ' ', middle_name) like '%Ива%');
此查询速度更快,因为没有执行其他操作
explain analyze
select * from person
where (last_name like '%Ива%') or (first_name like '%Ива%') or (middle_name like '%Ива%');
虽然不是具体的答案,但以下内容可能会帮助您得出一些结论:
调用
concat
连接三个字符串,或使用||
运算符,导致 postgres 必须分配一个新缓冲区来保存连接的字符串,然后复制字符放入其中。这必须为每一行完成。然后必须在最后释放缓冲区。在将三个条件组合在一起的情况下,postgres 可能只需要评估其中的一个或两个就可以决定是否必须包含该行。
与对
concat
的函数调用相比,使用||
运算符的表达式求值可能更有效,或者可能更容易优化。如果发现内部运算符有一些特殊情况处理,我不会感到惊讶。如评论所述,您的样本太小,无论如何都无法得出正确的结论。在几分之一毫秒的水平上,其他噪声因素可能会扭曲结果。
到目前为止您所观察到的内容很有趣,但并不重要。连接字符串的开销很小。
这些表达式之间更重要的区别不会显示在没有索引的最小测试用例中。
前两个例子不是sargable(除非你建立一个定制的表达式索引):
where concat(last_name, ' ', first_name, ' ', middle_name) like '%Ива%'
where (last_name || ' ' || first_name || ' ' || middle_name) like '%Ива%'
虽然这个是:
where last_name like '%Ива%' or first_name like '%Ива%' or middle_name like '%Ива%'
也就是说,它可以使用普通的三元组索引来产生很好的效果(列的顺序在 GIN 索引中并不重要):
CREATE INDEX some_idx ON person USING gin (first_name gin_trgm_ops
, middle_name gin_trgm_ops
, last_name gin_trgm_ops);
说明:
- PostgreSQL LIKE query performance variations
如果可能为 NULL,则测试不正确
concat()
通常比使用 ||
的简单字符串连接稍微贵一些。它也是 不同的 :如果任何输入字符串为 NULL,则在您的第二种情况下连接的结果也为 NULL,但在您的第一种情况下不是,因为 concat()
只是忽略NULL 值 - 但您仍然会在结果中得到一个无用的 space 字符。
详细解释:
- Combine two columns and add into one new column
如果您正在寻找干净、优雅的表达方式(成本大致相同),请改用 concat_ws()
:
concat_ws( ' ', last_name, first_name, middle_name)