【问题标题】:MySQL PerformanceMySQL 性能
【发布时间】:2009-02-14 05:10:21
【问题描述】:

最近我在缓存到内存缓存之前的查询一直在处理!在此示例中,它需要 10 秒。在这种情况下,我要做的就是获得 10 个最近的热门歌曲。

我感觉它加载了所有 125,592 行然后只返回 10,对吗?

#User@Host:root[root]@localhost[]
# Query_time: 10 Lock_time: 0 Rows_sent: 10 Rows_examined: 125592
SELECT * FROM hits WHERE campaign_id = 30 ORDER BY id DESC LIMIT 10;

这是另一个慢查询:

# 时间:090214 5:00:40 # 用户@主机:root[root]@localhost[] # Query_time: 3 Lock_time: 0 Rows_sent: 1 Rows_examined: 128879 SELECT count(DISTINCT(ip_address)) AS count_distinct_ip_address FROM `hits` WHERE (campaign_id = 30);

运行查询 phpMyAdmin 时,需要 1.3395 秒。虽然只是做一个SELECT * FROM hits 只需要 0.0001 秒。 我觉得很奇怪,返回所有匹配项所需的时间比对它们进行排序要少,或者仅仅是我正在对它们进行排序?

对于那些想看我的桌子的人:

CREATE TABLE `hits` (
  `id` int(11) unsigned NOT NULL auto_increment,
  `hostname` varchar(255) NOT NULL,
  `url` tinytext 不为空,
  `user_agent` tinytext NOT NULL,
  `created_at` 时间戳 NOT NULL 默认 CURRENT_TIMESTAMP,
  `ip_address` varchar(15) NOT NULL,
  `campaign_id` int(11) NOT NULL,
  主键(`id`),
  KEY `campaign_id`(`campaign_id`),
  KEY `ip_address` (`ip_address`)
);

【问题讨论】:

  • 您是否对这些查询进行了解释?
  • 我正要发布同样的问题...请发布运行 EXPLAIN SELECT * FROM hits WHERE campaign_id = 30 ORDER BY id DESC LIMIT 10 时返回的详细信息;
  • 我建议你避免使用 phpMyAdmin 做任何事情;这不是一个有用的工具。它的行为太不可预测了,并且有很多命令行客户端中不存在的错误。

标签: mysql ruby-on-rails caching memcached


【解决方案1】:

您的campaign_id 索引似乎选择性低,即。 e.有很多具有此值的记录。

订购这么多记录需要很长时间。

尝试在PRIMARY KEY 上使用INDEX SCAN 进行订购:

/* Edited, as MySQL does not use live feed from the derived source with ORDER BY */
SELECT *
FROM hits
WHERE IFNULL(campaign_id, campaing_id) = 30
ORDER BY id DESC
LIMIT 10;

至于您的第二个查询,可以做的事情并不多,因为无论如何您都需要对整个campaign_id = 30 进行完整扫描,无论是TABLE SCAN 还是INDEX SCAN

其实TABLE SCAN可以更快:

SELECT count(DISTINCT(ip_address)) AS count_distinct_ip_address
FROM `hits`
WHERE IFNULL(campaign_id, campaign_id)  = 30;

如果不是,你可以在(campaign_id, ip_address)上创建一个索引,并使用一个技巧在这个索引上模仿INDEX GROUP BY

CREATE INDEX ix_hits_campaign_ip ON hits(campaign_id, ip_address)

SELECT SUM(cnt)
FROM (
SELECT CASE WHEN @r = ip_address THEN 0 ELSE 1 END AS cnt,
  @r := ip_address
FROM
  (SELECT @r:='') r,
  (
  SELECT ip_address
  FROM hits
  WHERE campaign_id = 30
  ORDER BY ip_address
  ) i
) o

这里的技巧很简单:我们不需要结果,只需要一个计数,所以不需要扫描实际值。索引扫描就足够了。

不幸的是,尽管 MySQL 文档中提到 here 关于松散索引扫描,但它们实际上并不适用于复合索引。这就是为什么我们需要模仿INDEX SCAN WITH GROUP BY

我们通过强制 MySQL 使用INDEX RANGE SCAN 来检索所有具有campaign_id = 30 的记录,这些记录按ip_address 排序。然后我们使用会话变量 @r 在第一个子查询中初始化为空字符串来计算 DISTINCT ip_address'es。

在第一个字段中,当前一个ip_address(存储在变量中)等于当前值时,我们将变量设置为0;否则我们将其设置为1。在第二个字段中,我们将 ip_address 的当前值分配给变量。

最后,我们在第一个字段中检索SUM,这当然会给我们COUNT (DISTINCT ip_address)

【讨论】:

  • 这些查询比上面的查询花费了更长的时间,我只好再搞砸了。
  • 您的行中有多少百分比是campaign_id = 30?
  • 我很感激!与 0.03 相比,它花了 0.00 秒,所以我可以肯定地看到一些性能改进。
  • 想更详细地解释您上次查询中的技巧吗?我会对它的工作原理感兴趣。
【解决方案2】:

(campaign_id,id) 上的索引应该可以很好地处理第一个索引。但不同的是有点棘手......

编辑: MySQL 不会为一个查询使用多个索引;所以是的,您需要 一个 索引来涵盖查询中涉及的所有字段。

【讨论】:

  • 我已经这样做了,看看主键和campaign_id的键?
  • ID 上缺少索引也是问题所在。 WHEREcampaign_id = 30 被索引覆盖,但是:ORDER BY id DESC 没有,所以服务器必须加载所有匹配campaign_id=3-的行,按id排序,然后获取前10
  • MySQL 确实在一个查询中进行了多个索引组合。例如,两个列上的 WHERE 子句,其中有 2 个单独的查询 - 它可以使用两个索引。
  • @Chris:“2 个单独的查询”是如何执行“一个查询中的多个索引组合”的?
【解决方案3】:

如果查询处理时间过长,通常是因为缺少索引、磁盘 IO 不佳或其他一些瓶颈。一个有 120 000 行的表并不是很多数据,查询真的不应该花那么长时间。我真的会检查磁盘 io。

上面的答案 1 是加快查询 1 的一种方法。要加快查询 2,您可能需要创建一个聚合表,该表会随每次点击更新,或者每晚在批处理运行中更新,然后您可以添加尚未聚合的天数。日期范围的索引应该会相对较快。

您还应该针对您的查询运行“解释”并查看它使用的索引(如果有)。您为 mysql 使用什么存储引擎?这也会产生影响。如果您使用的是 MISAM 存储引擎并同时进行插入和读取,这可能会对性能产生很大影响。

通过定期对较大的表格运行“分析”来确保更新您的表格统计信息。这有助于查询引擎选择最优的查询计划。

【讨论】:

  • 我正在使用 MyISAM,您建议我将其更改为什么?
  • 还要注意,我的 my.cnf 文件中有这个:skip-external-locking 和 skip-locking
【解决方案4】:

您需要使用 EXPLAIN 来了解它是如何执行您的查询的。您需要在生产或类似生产的数据上执行此操作,但显然不应该在生产系统上执行此操作(当然,您需要在开发和生产中使用相同的软件进行此练习) - 以上表明它正在执行全表扫描;这可能是因为它没有任何可以使用的索引,或者它选择不使用它们,因为它们的基数低等。

然后您需要评估可以添加哪些索引来改进它,尝试添加它们,再次对其进行测试,然后通过检查添加索引是否不会破坏应用程序中的任何其他内容并且不会破坏来尝试对更改进行 QA回归其他地方的表现。您需要分析空间和性能影响——同样,这可以通过测试系统上的生产类数据来完成(性能测试当然需要在生产规格的硬件上进行)。

一旦您确定添加索引是正确的做法,您就可以像往常一样将这些更改整合到软件版本中。不过要注意大表上的 ALTER TABLE,它可能需要一些时间并且会阻止对表的写入(但是 120k 行可能不是一个大表)。在推出更改之前,请确保您知道需要多长时间以及它将对生产产生什么影响。

【讨论】:

    【解决方案5】:

    只是猜测。

    SELECT * FROM hits WHERE (campaign_id = 30 AND id > 0) ORDER BY id DESC LIMIT 10;
    

    希望 MySQL 将合并索引。祝你好运。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-12
      • 2014-02-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多