【问题标题】:Is InnoDB sorting really THAT slow?InnoDB 排序真的那么慢吗?
【发布时间】:2010-11-28 13:24:26
【问题描述】:

我的所有表都在 myISAM 中,但是当我长时间运行更新作业时,表级锁定开始扼杀我。我将我的主表转换为 InnoDB,现在我的许多查询需要 1 分钟以上才能完成,而它们在 myISAM 上几乎是瞬时完成的。它们通常卡在Sorting result 步骤中。我是不是做错了什么?

例如:

SELECT * FROM `metaward_achiever` 
 INNER JOIN `metaward_alias` ON (`metaward_achiever`.`alias_id` = `metaward_alias`.`id`) 
 WHERE `metaward_achiever`.`award_id` = 1507  
 ORDER BY `metaward_achiever`.`modified` DESC 
 LIMIT 100  

现在大约需要 90 秒。这是描述:

+----+-------------+-------------------+--------+-------------------------------------------------------+----------------------------+---------+---------------------------------+-------+-----------------------------+
| id | select_type | table             | type   | possible_keys                                         | key                        | key_len | ref                             | rows  | Extra                       |
+----+-------------+-------------------+--------+-------------------------------------------------------+----------------------------+---------+---------------------------------+-------+-----------------------------+
|  1 | SIMPLE      | metaward_achiever | ref    | metaward_achiever_award_id,metaward_achiever_alias_id | metaward_achiever_award_id | 4       | const                           | 66424 | Using where; Using filesort | 
|  1 | SIMPLE      | metaward_alias    | eq_ref | PRIMARY                                               | PRIMARY                    | 4       | paul.metaward_achiever.alias_id |     1 |                             | 
+----+-------------+-------------------+--------+-------------------------------------------------------+----------------------------+---------+---------------------------------+-------+-----------------------------+

现在我的大量查询似乎卡在“排序结果”步骤中:

mysql> show processlist;
+--------+------+-----------+------+---------+------+----------------+------------------------------------------------------------------------------------------------------+
| Id     | User | Host      | db   | Command | Time | State          | Info                                                                                                 |
+--------+------+-----------+------+---------+------+----------------+------------------------------------------------------------------------------------------------------+
| 460568 | paul | localhost | paul | Query   |    0 | NULL           | show processlist                                                                                     | 
| 460638 | paul | localhost | paul | Query   |    0 | Sorting result | SELECT `metaward_achiever`.`id`, `metaward_achiever`.`modified`, `metaward_achiever`.`created`, `met | 
| 460710 | paul | localhost | paul | Query   |   79 | Sending data   | SELECT `metaward_achiever`.`id`, `metaward_achiever`.`modified`, `metaward_achiever`.`created`, `met | 
| 460722 | paul | localhost | paul | Query   |   49 | Updating       | UPDATE `metaward_alias` SET `modified` = '2009-09-15 12:43:50', `created` = '2009-08-24 11:55:24', ` | 
| 460732 | paul | localhost | paul | Query   |   25 | Sorting result | SELECT `metaward_achiever`.`id`, `metaward_achiever`.`modified`, `metaward_achiever`.`created`, `met | 
+--------+------+-----------+------+---------+------+----------------+------------------------------------------------------------------------------------------------------+
5 rows in set (0.00 sec)

为什么这个简单的更新会卡住 49 秒?

如果有帮助,这里是模式:

| metaward_alias | CREATE TABLE `metaward_alias` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL,
  `string_id` varchar(255) DEFAULT NULL,
  `shortname` varchar(100) NOT NULL,
  `remote_image` varchar(500) DEFAULT NULL,
  `image` varchar(100) NOT NULL,
  `user_id` int(11) DEFAULT NULL,
  `type_id` int(11) NOT NULL,
  `md5` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `string_id` (`string_id`),
  KEY `metaward_alias_user_id` (`user_id`),
  KEY `metaward_alias_type_id` (`type_id`)
) ENGINE=InnoDB AUTO_INCREMENT=858381 DEFAULT CHARSET=utf8 | 

| metaward_award | CREATE TABLE `metaward_award` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL,
  `string_id` varchar(20) NOT NULL,
  `owner_id` int(11) NOT NULL,
  `name` varchar(100) NOT NULL,
  `description` longtext NOT NULL,
  `owner_points` int(11) NOT NULL,
  `url` varchar(500) NOT NULL,
  `remote_image` varchar(500) DEFAULT NULL,
  `image` varchar(100) NOT NULL,
  `parent_award_id` int(11) DEFAULT NULL,
  `slug` varchar(110) NOT NULL,
  `true_points` double DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `string_id` (`string_id`),
  KEY `metaward_award_owner_id` (`owner_id`),
  KEY `metaward_award_parent_award_id` (`parent_award_id`),
  KEY `metaward_award_slug` (`slug`),
  KEY `metaward_award_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=122176 DEFAULT CHARSET=utf8 | 

| metaward_achiever | CREATE TABLE `metaward_achiever` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `modified` datetime NOT NULL,
  `created` datetime NOT NULL,
  `award_id` int(11) NOT NULL,
  `alias_id` int(11) NOT NULL,
  `count` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `metaward_achiever_award_id` (`award_id`),
  KEY `metaward_achiever_alias_id` (`alias_id`)
) ENGINE=InnoDB AUTO_INCREMENT=77175366 DEFAULT CHARSET=utf8 | 

这些在我的 my.cnf 中

innodb_file_per_table
innodb_buffer_pool_size = 2048M
innodb_additional_mem_pool_size = 16M
innodb_flush_method=O_DIRECT

【问题讨论】:

    标签: sql mysql database innodb myisam


    【解决方案1】:

    排序是由数据库服务器完成的,而不是 MySQL 中的存储引擎。

    如果在这两种情况下,引擎都无法以已经排序的形式提供结果(这取决于使用的索引),那么服务器需要对它们进行排序。

    MyISAM / InnoDB 可能不同的唯一原因是行返回的顺序可能会影响数据的排序方式 - MyISAM 可以以“更排序”的顺序返回数据某些情况(反之亦然)。

    不过,对 60k 行进行排序不会花费很长时间,因为它是一个非常小的数据集。你确定你的排序缓冲区设置得足够大吗?

    使用磁盘上的 filesort() 而不是内存中的文件排序要慢得多。但是,引擎应该对此没有任何影响。 filesort 不是引擎功能,而是 MySQL 的核心功能。实际上,filesort 确实在很多方面都很糟糕,但通常不会那么慢。

    【讨论】:

    • +1 我在上面评论了这个效果,但错过了你的评论。马上!
    【解决方案2】:

    Should you move from MyISAM to Innodb ? 中所写(这是最近的):

    Innodb 需要调优 作为关于 MyISAM 到 Innodb 迁移的最后一点,我应该提到 Innodb 调优。 Innodb 需要调优。真的。许多应用程序的 MyISAM 可以很好地使用默认值。我已经看到数百 GB 的数据库使用默认设置的 MyISAM 运行,并且运行良好。 Innodb 需要资源,并且它不能很好地使用默认值。从默认值调整 MyISAM 很少会获得超过 2-3 倍的收益,而对于 Innodb 表,特别是对于写入密集型工作负载,它可能会高达 10-50 倍。详情请查看here

    所以,关于 MySQL Innodb Settings,作者在Innodb Performance Optimization Basics写道:

    最重要的是:

    innodb_buffer_pool_size 70-80% 内存是一个安全的赌注。我设置为12G 在 16GB 盒子上。

    更新:如果您正在寻找更多 详细信息,请查看详细指南 tuning innodb buffer pool

    innodb_log_file_size – 这取决于 你的恢复速度需要256M 似乎是一个很好的平衡 合理的恢复时间和良好的 性能

    innodb_log_buffer_size=4M 4M 好 在大多数情况下,除非你是管道 在这种情况下,Innodb 的大块 增加一点。

    innodb_flush_log_at_trx_commit=2 如果您不关心 ACID,并且在操作系统完全崩溃的情况下可以在最后一秒或两秒内丢失事务,则设置此值。它可以产生巨大的影响,尤其是对大量的短写事务。

    innodb_thread_concurrency=8 即使当前的 Innodb 可扩展性修复具有有限的并发性也有帮助。实际数字可能更高或更低,具体取决于您的应用程序,默认为 8 是不错的开始

    innodb_flush_method=O_DIRECT 避免双重缓冲并减少交换压力,在大多数情况下,此设置可提高性能。不过,如果您没有电池备份 RAID 缓存,请小心,因为写入 IO 可能会受到影响。

    innodb_file_per_table – 如果您没有太多表,请使用此选项,这样您就不会出现无法回收的不受控制的 innodb 主表空间增长。这个选项是在 MySQL 4.1 中添加的,现在足够稳定可以使用。

    还要检查您的应用程序是否可以在 READ-COMMITED 隔离模式下运行——如果可以——将其设置为默认设置为 transaction-isolation=READ-COMMITTED。此选项具有一些性能优势,尤其是在锁定 5.0 以及 MySQL 5.1 和行级复制附带的更多方面。

    为了记录,mysqlperformanceblog.com 背后的人运行了一个比较 Falcon、MyISAM 和 InnoDB 的基准测试。基准测试确实应该突出 Falcon,除了 InnoDB 赢得了这一天,几乎所有测试的每秒查询数都超过了 Falcon 和 MyISAM:InnoDB vs MyISAM vs Falcon benchmarks – part 1

    【讨论】:

    • 感谢您的帖子。我确实已经设置了许多这些选项(请参阅问题的底部),并且 mysql 实例确实占用了我大约 50% 的内存,但在 InnoDB 与 MyISAM 上的表现仍然很糟糕。
    【解决方案3】:

    这是 MySQL 必须手动排序的大型结果集(66,424 行)。尝试向 metaward_achiever.modified 添加索引。

    MySQL 4.x 有一个限制,它只允许 MySQL 每个表使用一个索引。由于它使用 metaward_achiever.award_id 列上的索引进行 WHERE 选择,因此它也不能使用 metaward_achiever.modified 上的索引进行排序。我希望您使用的是 MySQL 5.x,它可能对此有所改进。

    你可以通过解释这个简化的查询来看到这一点:

    SELECT * FROM `metaward_achiever` 
     WHERE `metaward_achiever`.`award_id` = 1507  
     ORDER BY `metaward_achiever`.`modified` DESC 
     LIMIT 100
    

    如果您可以同时使用 WHERE 选择和排序的索引来获取此信息,那么您已经设置好了。

    您还可以使用 metaward_achiever.award_id 和 metaward_achiever 创建一个复合索引。如果 MySQL 不使用它,那么您可以提示它或删除刚刚 Award_id 上的那个。

    或者,如果您可以摆脱 metaward_achiever.id 并将 metaward_achiever.award_id 作为您的主键并在 metaward_achiever.modified 上添加一个键,或者更好地将 metaward_achiever.award_id 与 metaward.modified 结合您的主键,那么您'会很好的。

    您可以尝试通过修改设置来优化文件排序。不幸的是,我没有这方面的经验,因为我们的 DBA 处理配置,但您可能想查看这个很棒的博客: http://www.mysqlperformanceblog.com/

    这是一篇关于文件排序的文章: http://s.petrunia.net/blog/?p=24

    【讨论】:

    • 使用 myISAM 的文件排序是否比使用 InnoDB 更快?他们不应该是一样的吗?
    • @Paul - 正确。在某些情况下可能存在一些感知差异,因为预排序数据的排序速度更快(并且 InnoDB 在物理上维护按主键排序的行),但在内部它始终工作相同,因为它不是在存储引擎级别处理的。
    【解决方案4】:

    我的猜测是您可能没有配置超出默认值的 InnoDB 设置。你应该做一个快速的谷歌来设置你的 InnoDB 选项。

    导致我开箱即用的最明显性能问题是innodb_buffer_pool_size。这应该设置为机器内存的 50-80%。默认情况下,它通常只有几 MB。将其方式启动,您应该会看到明显的性能提升。

    也可以看看innodb_additional_mem_pool_size

    Start here,但也可以在 Google 上搜索“innodb 性能调整”。

    【讨论】:

    • 我有这些设置:innodb_file_per_table innodb_buffer_pool_size = 2048M innodb_additional_mem_pool_size = 16M innodb_flush_method=O_DIRECT
    【解决方案5】:

    据我的记忆,MySQL 的查询优化器并不好。尝试子选择而不是直接连接。

    SELECT * FROM (SELECT * FROM `metaward_achiever` 
                   WHERE `metaward_achiever`.`award_id` = 1507) a
    INNER JOIN `metaward_alias` ON (a.`alias_id` = `metaward_alias`.`id`) 
    ORDER BY a.`modified` DESC 
    LIMIT 100
    

    或类似的东西(上面未经测试的语法)。

    【讨论】:

    • 您的查询只需要 10 秒,但 myISAM 是毫秒级的。为什么?
    【解决方案6】:

    尝试为字段添加键:metaward_achiever.alias_idmetaward_achiever.award_idmetaward_achiever.modified 这将有很大帮助。并且不要在 varchar 字段上使用键,这会增加插入和更新的时间。此外,您似乎在成就表中有 77M 记录,您可能需要关心 innodb 优化。关于如何为它设置内存限制有很多很好的导师。

    【讨论】:

    • 我有两个键:KEY metaward_achiever_award_id (award_id), KEY metaward_achiever_alias_id (alias_id)。你真的应该把键放在日期时间字段上吗?至于 varchar 键,我真的不会太频繁地更新这些表,但这就是我坚持 49 秒更新的原因吗?我在 varchar 字段上选择了很多,所以我认为键会有所帮助。
    • 尝试将字段索引在一起:metaward_achiever_combined (award_id, alias_id, modified)。为我的查找时间创造了奇迹。
    猜你喜欢
    • 2011-11-12
    • 2012-02-13
    • 2013-02-17
    • 1970-01-01
    • 2013-05-07
    • 2020-01-15
    • 2010-12-27
    • 2014-12-13
    • 1970-01-01
    相关资源
    最近更新 更多