【问题标题】:Performance difference of query查询的性能差异
【发布时间】:2016-02-02 13:23:15
【问题描述】:

首先,我们的环境是 PHP + MYSQL。 我们有一个表格 Articles,它是一个用来保存文本文章的表格。大约有15000条记录。 我们的查询存在性能问题:

SELECT article_id, article_title, article_status, 
     article_date_time, article_publish_date 
FROM articles 
WHERE article_status IN ('approved') 
   AND (article_publish_date <= now()) 
   AND ((article_expiry_date = '0000-00-00') OR 
       (article_expiry_date <> '0000-00-00' 
        AND article_expiry_date >= now())) 
   AND articles_id IN (1, 2, 3... a list of about 9,000 possible ID's)
GROUP BY article_id
ORDER BY article_date_time DESC LIMIT 0,5;

在我们的测试站点(db server 和 web server 在同一台机器上),如果我第一次运行查询,查询的执行时间大约是 30 秒。 还是在测试站点,如果我只是刷新页面第二次运行查询,查询的执行时间大约是0.2秒。

如果一直刷新,执行时间还是0.2秒左右。但是如果我停止大约 15 分钟,执行时间又会是 30 秒,然后是 0.2 秒...

问题 1 来了:第一次执行和第二次执行之间的巨大差异是什么?缓存?如果是这样,那么它是如何产生影响的呢?

仍然是同一个查询,在我们的实时站点中(仍然,db server 和 web server 在同一台机器上),查询的执行时间约为 3 秒。但是无论您运行多少次查询,时间都在 3 秒左右。

test db 是 live db 的备份,所以 db 的差异不应该造成如此不同的结果。

那么问题2来了:为什么live site的执行时间不是30秒也不是0.2秒?为什么在第二次执行时它不会改变?

有人可以帮忙吗?

【问题讨论】:

  • mysql OR sql-server?它们不一样
  • 是mysql服务器吗?使用什么数据库类型?创新数据库?对于缓存问题, - 检查缓存参数,有可能在生产环境中您必须减小查询缓存大小。另外,也许您在生产中的数据库中有一些锁定?
  • 您必须在每个服务器中进行不同的配置。无论如何,您的 WHERE 子句似乎在浪费时间进行一些比较(?)可能的 id 来自哪里?
  • 如果您主要关心的是性能,我会先看看 SQL 本身。 9000个id的列表?您应该使用这些 id、主键、索引创建一个表,并且会提高速度。还有为什么你有 article_expiry_date &lt;&gt; '0000-00-00' AND ?你可以忽略它,这是暗示的。
  • 如果 id 碰巧是连续的WHERE id BETWEEN x and y,您可以进行范围搜索

标签: php mysql myisam


【解决方案1】:

问题几乎可以肯定是In (... list of 9000 Id ....) 第一次执行查询时,SQL 查询处理器必须从磁盘读取数据。在此过程中,它可能将数据存储在高速缓存中。第二次,数据还在缓存中,所以都是RAM访问。 In 子句(因为它被转换成 9000 次重复 Or articles_id = id1 Or articles_id = id2 Or articles_id = id3 ... 需要很长时间。 (虽然我不完全确定为什么......

我建议(至少,作为确认这一点的测试)将这 9000 个 ID 放入一个表中,然后重写查询以加入该表。然后,如果该测试表明这就是问题所在,请重写您的查询。

我对 MySQL/Php 的了解还不够,无法知道这是否可行。但在带有 .Net 的 SQL Server 中,您可以在客户端 (ADO.Net) 代码中,创建整数或字符串 Id 值的集合,并在单个 SQL 或存储过程参数中将该集合传递给数据库,其中将像一张表一样被使用,(例如,您可以在 SQL 连接语句中引用 t )您可能想研究 MySQL,看看在 PHP/MySQL 中是否可以这样做或类似的东西。否则,请考虑创建这 9000 + Id 的分隔列表并将其传递给 MySQL 存储过程,然后在 SP 内部对其进行解析以将其转换为您可以加入的表。

【讨论】:

  • 您的建议在某种程度上似乎是有道理的,尤其是对于问题 1。我会试一试。您能否也给我关于问题 2 的任何提示:)?提前致谢。
  • 我敢打赌,加速是因为查询缓存,而不是文件系统缓存。
  • 有没有办法防止mysql使用文件系统缓存?我不想每次测试都等待 15 分钟...“SELECT NO_SQL_CACHE ...”?我认为它不起作用。
  • 我相信你可以将缓存大小设置为零。
  • 是的,你可以。请参阅更新的答案。要写在查询中,它是SELECT SQL_NO_CACHE ...-- 你的关键字错误。
【解决方案2】:

快速重复响应的原因是 MySQL 可以缓存查询;查询不仅第二次运行得更快:它根本没有运行,因为系统检测到它已经看到查询并且仍然有结果。要禁用查询缓存以进行测试,您可以set the server's query cache size to zero。要禁用单个查询的缓存,请在 SELECT 之后添加 SQL_NO_CACHE

9000个ID列表是性能缓慢的明显罪魁祸首;但他们是从哪里来的?如果它们在查询中被硬编码,您必须提前知道它们是什么。在这种情况下,更快的解决方案是修改表架构并添加一个记录 ID 是否合格的列。但正确的解决方案实际上取决于 ID 的确定方式。

编辑:由于文章 ID 列表来自一个复杂的查询,您应该将这两个查询合并为两个。最简单的方法是将 article-id 查询嵌入为子查询:

SELECT article_id, article_title, article_status, ...
FROM articles
WHERE ...
    AND article_id IN (SELECT article_id from <subquery conditions>)

但如果您不厌其烦地将查询重写为真正的联接,服务器将能够更好地优化您的查询。

其他(次要)问题:以下子句是多余的。

((article_expiry_date = '0000-00-00') OR 
   (article_expiry_date <> '0000-00-00' 
    AND article_expiry_date >= now()))

如果前半部分为假,第二个比较将始终为真;因此您应该将其简化为

(article_expiry_date = '0000-00-00' OR article_expiry_date >= now())

要查看您的程序将时间花在哪里,请向服务器发送一个手工制作的查询,前面带有 EXPLAIN,然后研究结果:

EXPLAIN SELECT article_id, article_title, article_status,
    ...

【讨论】:

  • 这些 ID 是根据变量输入从文章表中过滤的结果。我们先得到过滤后的结果,然后将它们分解成字符串,然后将字符串传递给上面的查询。
  • 那么,如果您可以将这两个查询合二为一,那么您肯定会提高性能:用表连接上的附加条件替换 ID 列表。
  • 并确保您经常使用的所有内容都已正确编入索引。
  • 我明白了。只是获取ID的逻辑太复杂,在其他地方使用。我有点希望添加索引或更改查询的其他部分会提高性能,这样我就不需要更改复杂的逻辑。但是现在看来ID的列表是这个的重点..,
  • 索引当然应该有所帮助——如果你走加入路线,你也需要它们;但是在 9000 行表上节省往返行程也很重要。如果代码很复杂,我肯定会尝试对其进行结构化,以便文章 id 查询逻辑在您需要的任何地方由单个函数生成,并根据需要嵌入到更大的查询中。这确实意味着你必须接触复杂的代码来重构它......
【解决方案3】:

我认为首先尝试优化查询很重要,而不是回答为什么它在两台服务器上以不同的时间运行的问题。 首先,您需要避免与IN 运算符一起使用的大量文字。

我建议添加另一个字段 表示此in 操作的结果:

ALTER TABLE articles ADD (
   flag int
);

UPDATE articles
SET   article_flag =
  CASE 
    WHEN article_id IN (1, 2, 3... a list of about 9,000 possible IDs) THEN 1
    ELSE 0
  END;

COMMIT;

如果还没有完成,请确保在article_date_time 上建立索引:

CREATE INDEX idx_article_date_time ON articles(article_date_time);

然后在没有group by 和一个冗余条件的情况下使用这个查询:

SELECT article_id, article_title, article_status, 
       article_date_time, article_publish_date 
FROM   articles 
WHERE  article_status = 'approved'
       article_flag = 1
   AND article_publish_date <= now()
   AND (   article_expiry_date = '0000-00-00' 
        OR article_expiry_date >= now()
       ) 
ORDER BY article_date_time DESC LIMIT 0,5;

如果你这样做,我预计性能会有所提高。

【讨论】:

  • 感谢您的详细建议。我已经在 article_date_time 上添加了索引,但上面的 2 个问题涵盖了索引的真正改进。我会尝试一下您的建议并及时通知您。
猜你喜欢
  • 1970-01-01
  • 2011-03-30
  • 1970-01-01
  • 2019-08-06
  • 1970-01-01
  • 2011-02-02
  • 2020-02-04
  • 1970-01-01
  • 2023-03-18
相关资源
最近更新 更多