具有复杂查询匹配模式的 MySQL 与 PostgreSQL 性能

Posted

技术标签:

【中文标题】具有复杂查询匹配模式的 MySQL 与 PostgreSQL 性能【英文标题】:MySQL vs PostgreSQL performance with a complex query matching patterns 【发布时间】:2011-04-16 22:34:27 【问题描述】:

我有一个复杂的数据库,大约有 30 个表。一个表有超过 500,000 行,另一个超过 15,000 行,我在一个单独的数据库中使用这两个表,直到今天我决定只在一个数据库中实现。

在今天之前,500,000 行的表在 mysql 数据库中,而 15,000 行的表在 PostgreSQL 中。在一个频繁使用的页面中,这是 php 基准测试的结果:

getSimilarAvaiable - 0.0287 s
getUnavaiable - 0.27 s
ProcessDataOfUnavaiable - 1.4701 s
Process - 1.8622 s
TotalPageTime - 3.631 s

在我将所有内容迁移到 PostgreSQL 并使用相同的 SQL 代码而不做任何更改后,同一页面的结果是这样的:

getSimilarAvaiable - 2.7465 s
getUnavaiableCars - 9.0763 s
ProcesseDataOfUnavaiableCars - 1.4167 s
ProcessCars - 1.7207 s
TotalPageTime - 14.9602 s

我把所有东西都放在 MySQL 中,相同的索引,所有东西,但我不明白为什么会有这么大的差异。我应该怎么做才能优化它?

编辑:现在解释得更好了。

500.00 表由以下结构组成:

id - bigint (primary key)
plate- varchar(10) Unique key
manufacturer - varchar(30)
vin - varchar(30)

主要查询是这样的:

SELECT plate, vin, 1 as n, substr(plate,1,2) as l 
FROM imtt_vin WHERE substr(plate,1,1) >= 'A' and substr(plate,1,1) <= 'Z' AND
(manufacturer ILIKE '%".self::$Manufacturer."%') AND vin LIKE ?
UNION
SELECT plate, vin, 3 as n, substr(plate,4,2) as l 
FROM imtt_vin WHERE substr(plate,4,1) >= 'A' and substr(plate,4,1) <= 'Z' AND
(manufacturer ILIKE '%".self::$Manufacturer."%') AND vin LIKE ?
UNION
SELECT plate, vin, 2 as n, substr(plate,7,2) as l 
FROM imtt_vin WHERE substr(plate,7,1) >= 'A' and substr(plate,7,1) <= 'Z' AND 
(manufacturer ILIKE '%".self::$Manufacturer."%') AND vin LIKE ?
ORDER BY n, l, plate;

EDIT2:使用复杂的单个查询进行测试,我将其从 15 秒减少到 8/9 秒。即便如此,这对我来说也太过分了。

【问题讨论】:

@David Believe me 30 表和 500,000 行远非复杂 :) 你是在 MySQL 还是 InnoDB 中使用 MyISAM? MyISAM 速度更快,但支持的功能更少。 没有看到您的任何查询或涉及的表和索引的结构,任何人都很难帮助您。 使用 EXPLAIN 查看查询是如何执行的。也可以使用 substr() 创建一些索引,这可能会改善很多:EXPLAIN 会显示给你。 您的程序的版本?索引的定义? EXPLAIN / EXPLAIN ANALYZE 的输出?表只读?或者写操作的频率如何? 【参考方案1】:

您需要发布 EXPLAIN yourquery (for mysql) 和 EXPLAIN ANALYZE yourquery (for postgres) ;没有它,就不可能说任何相关的东西。

也选择 pg_relation_size('imtt_vin')

例如,“?”的值是什么?在这个查询中?

SELECT plate, vin, 1 as n, substr(plate,1,2) as l 
FROM imtt_vin WHERE substr(plate,1,1) >= 'A' and substr(plate,1,1) <= 'Z' AND
(manufacturer ILIKE '%".self::$Manufacturer."%') AND vin LIKE ?

我不知道你在哪里工作的车牌,但是这部分:

WHERE substr(plate,1,1) >= 'A' and substr(plate,1,1) <= 'Z'

可能会选择数据库中的所有行,因此其唯一目的是消耗 CPU 周期。你至少可以像这样重写它(和所有其他的)以避免调用 substr() :

WHERE substr(plate,1,1) BETWEEN 'A' AND 'Z'

当然,当它没有用时,删除条件。

然后我们有:

manufacturer ILIKE '%".self::$Manufacturer."%'

糟糕的数据库设计:世界上有 500.000 家汽车制造商吗?可能不是。您应该将制造商放在另一个表中并使用外键。这会将这种不可索引的条件变成可索引的条件。

对于其余部分,请发布 EXPLAIN / EXPLAIN ANALYZE。

【讨论】:

条件实际上不是“不可索引”。有一种方法。在我的回答中概述了它。【参考方案2】:

如果您在 MySQL 中使用 MyISAM,理论上可以解释性能差异(因为关于您的数据库设计和执行的查询没有太多公开)。关于两个 RDBMS 之间的交叉性能,我建议您查看 this comparison page(锚定到 MyISAM 部分)。

【讨论】:

是的,我使用 MyISAM。我在第一篇文章中发布了我使用的结构和查询。 @DavidMagalhães:MyISAM 对于原始的蛮力查询非常快。我认为我为 PostgreSQL 概述的索引仍然可以解决它。【参考方案3】:

MySQL 默认使用更多内存。我认为它被 def install 分配使用超过 256MB。不确定确切的数字。 PostgreSQL 默认设置为使用 32MB 之类的大小。尝试在配置文件中将每个内存增加到 1GB,然后运行基准测试并返回给我们。

【讨论】:

默认使用 16MB 或 32MB。每个已经达到 128MB,没有任何变化。 @DavidMagalhães:您可以调整很多不同的设置。 Postgres Wiki 是您入门的好地方。【参考方案4】:

在我看来,您可能没有更新 Postgres 数据库的统计信息。如果统计数据不当,数据库的性能将不会很好。

【讨论】:

我激活了 autoVacuum,但什么也没发生 :( 您需要发出 VACUUM ANALYZE 语句。 Autovacuum 对这样的大批量插入没有帮助。 @Denis:Autovacuum 分析所有表(使用默认设置),但可能需要一些时间。如果您在批量插入后立即运行查询,则只需要手动ANALYZE【参考方案5】:

查询

(
SELECT 1 AS n, left(plate, 2) AS l, plate, vin
FROM   imtt_vin
WHERE  left(plate, 1) BETWEEN 'A' AND 'Z'
AND    manufacturer ILIKE '%".self::$Manufacturer."%'
AND    vin LIKE ?   -- You probably mean: vin = ?
ORDER  BY l, plate
)

UNION ALL
(
SELECT 3 AS n, substr(plate, 4, 2) AS l, plate, vin
FROM   imtt_vin
WHERE  substr(plate, 4, 1) BETWEEN 'A' AND 'Z'
AND    manufacturer ILIKE '%".self::$Manufacturer."%'
AND    vin LIKE ?
ORDER  BY l, plate
)

UNION  ALL ...
使用UNION ALLUNION 将用于折叠重复项,这显然不是这里的情况,而且会更昂贵。 由于您的前导 ORDER BY 项目是 n,因此对查询的各个部分进行排序可能更有效。为此需要额外的一组括号。 left (plate, 2)substr(plate, 1, 2) 快一点。仅适用于前导子字符串(您的第一个 SELECT)。

索引

默认B-tree index only works for left-anchored LIKE expressions。但trigram GiST 或 GIN 索引可用于非左锚定模式。您需要附加模块pg_trgm。在 PostgreSQL 9.1 或更高版本中使用 CREATE EXTENSION 为每个数据库安装一次。查阅旧版本的手册。

CREATE EXTENSION pg_trgm;

我没有太多信息可以继续,基本的partial GIN indexes 应该可以工作奇迹

CREATE INDEX imtt_vin_partial_gist_idx ON imtt_vin
USING  gin (manufacturer gin_trgm_ops)
WHERE  left(plate, 1) BETWEEN 'A' AND 'Z';

CREATE INDEX imtt_vin_partial_gist_idx ON imtt_vin
USING  gin (manufacturer gin_trgm_ops)
WHERE  substr(plate, 4, 1) BETWEEN 'A' AND 'Z';

-- more ...
我没有在索引中包含vin,因为您可能希望在其中使用相等运算符=。 部分索引上的谓词必须在查询中重复(或多或少),以便查询规划器了解该索引是适用的。 三元组索引适用于不区分大小写的匹配。 用EXPLAIN ANALYZE 测试索引是否被实际使用。如果是,查询时间应该是 毫秒,而不是秒。 索引维护的写入操作需要(少量)成本来提高速度。并且索引通常是磁盘上表大小的几倍。 你不能用 MySQL 做任何这些。

【讨论】:

【参考方案6】:

您仍然没有提供足够的信息——您有哪些索引、慢查询的 EXPLAIN ANALYZE 输出等。

关于优化示例查询的一些想法:

1: UTF-8 字符串函数一般不是很快。如果要加速字符串函数,请使用bytea 类型而不是 varchar 用于此列(或将整个数据库编码更改为SQL_ASCII,但这是不可取的)

2:根据您的查询,数据库可能必须遍历表中的所有行并为每个行计算这些字符串函数。

我不知道他们有多少匹配,所以索引可能没有用,但功能索引可能会帮助你:

 CREATE INDEX imtt_vin_plate_1 ON imtt_vin (substr(plate,1,1));
 CREATE INDEX imtt_vin_plate_4 ON imtt_vin (substr(plate,4,1));
 CREATE INDEX imtt_vin_plate_7 ON imtt_vin (substr(plate,7,1));

3:如果您可以容忍重复输出,请在查询中使用 UNION ALL 而不是 UNION - 这样可以为您节省一些处理较大结果集的时间。

4:尽可能避免LIKE/ILIKE

【讨论】:

以上是关于具有复杂查询匹配模式的 MySQL 与 PostgreSQL 性能的主要内容,如果未能解决你的问题,请参考以下文章

REGEX 匹配整数 6 到 10

mysql复杂查询

MySQL之正则表达式(REGEXP)

MySQL模糊匹配查询likeregexpin

具有多个模式和用户的连接池 Postgres

具有复杂 MySQL 查询的索引