【问题标题】:MySQL database row COUNT optimizationMySQL数据库行COUNT优化
【发布时间】:2015-12-23 04:21:44
【问题描述】:

我有一个 MySQL (5.6.26) 数据库,其中包含大量数据,但我在表连接时遇到了 COUNT 选择问题。

执行此查询大约需要 23 秒:

SELECT COUNT(0) FROM user
LEFT JOIN blog_user ON blog_user.id_user = user.id
WHERE email IS NOT NULL
AND blog_user.id_blog = 1

user 表是 MyISAM,包含用户数据,如 id、电子邮件、姓名等...

CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `email` varchar(100) DEFAULT '',
  `hash` varchar(100) DEFAULT NULL,
  `last_login` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`) USING BTREE,
  UNIQUE KEY `email` (`email`) USING BTREE,
  UNIQUE KEY `hash` (`hash`) USING BTREE,
  FULLTEXT KEY `email_full_text` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=5728203 DEFAULT CHARSET=utf8

blog_user 表是 InnoDB,仅包含 id、id_user 和 id_blog(用户可以访问多个博客)。 id 是 PRIMARY KEY 并且在 id_blog、id_user 和 id_blog-id_user 上有索引。

CREATE TABLE `blog_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `id_blog` int(11) NOT NULL DEFAULT '0',
  `id_user` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `id_blog_user` (`id_blog`,`id_user`) USING BTREE,
  KEY `id_user` (`id_user`) USING BTREE,
  KEY `id_blog` (`id_blog`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5250695 DEFAULT CHARSET=utf8

我删除了所有其他表,没有其他连接到 MySQL 服务器(测试环境)。

到目前为止我发现了什么:

  1. 当我从用户表中删除一些列时,查询的持续时间会更短(例如每个删除的列需要 2 秒)
  2. 当我从用户表中删除所有列(id 和电子邮件除外)时,查询的持续时间为 0.6 秒。
  3. 当我将 blog_user 表也更改为 MyISAM 时,查询持续时间为 46 秒。
  4. 当我将用户表更改为 InnoDB 时,查询持续时间为 0.1 秒。

问题是为什么 MyISAM 执行命令这么慢?

【问题讨论】:

  • 使用索引和内连接(如果可能)
  • 您是否在查询中使用了explain,看看它是否让您知道它在哪里花费时间?
  • MyISAM 必须从磁盘读取所有数据,遍历每一行并将其添加到计数中。据我所知,它使用硬件的方式与 InnoDB 不同(如果可以的话,InnoDB 会将其所有工作数据存储在 RAM 中)。对您来说最好的解决方案是同时拥有两个表 InnoDB 并避免从磁盘读取。
  • @Mjh 谢谢,我正在考虑这个,但不幸的是,我的桌面上有全文索引,它与 MyISAM 一起工作得更好。
  • 我怀疑为这两个表使用两个不同的存储引擎这一事实会对性能产生影响。

标签: php mysql database innodb myisam


【解决方案1】:

首先,您的查询中的一些 cmets(在稍微修复后):

SELECT COUNT(*)
FROM user u LEFT JOIN
     blog_user bu
     ON bu.id_user = u.id
WHERE u.email IS NOT NULL AND bu.id_blog = 1;

表别名有助于更轻松地编写和读取查询。更重要的是,您有一个LEFT JOIN,但您的WHERE 子句将它变成了INNER JOIN。所以,就这样写吧:

SELECT COUNT(*)
FROM user u INNER JOIN
     blog_user bu
    ON bu.id_user = u.id
WHERE u.email IS NOT NULL AND bu.id_blog = 1;

差异很重要,因为它会影响优化器可以做出的选择。

接下来,索引将帮助这个查询。我猜blog_user(id_blog, id_user)user(id, email) 是最好的索引。

列数影响原始查询的原因是它执行了大量 I/O。列越少,存储记录所需的页面就越少——查询运行的速度就越快。正确的索引应该更好、更一致地工作。

【讨论】:

  • 谢谢您,但不幸的是,此更改没有帮助。查询持续时间与原始查询大致相同。
【解决方案2】:

要回答真正的问题(为什么 myisam 比 InnoDB 慢),我不能给出权威的答案。

但这肯定与两个存储引擎之间更重要的区别之一有关:InnoDB 支持外键,而 myisam 不支持。外键对于连接表很重要。

我不知道定义外键约束是否会进一步提高速度,但可以肯定的是,它会保证数据的一致性。

另一个注意事项:您观察到删除列时时间会减少。这表明查询需要全表扫描。这可以通过在电子邮件列上创建索引来避免。 user.id 和 blog.id_user 希望已经有一个索引,如果没有,这是一个错误。参与外键的列,无论是否显式,都必须有一个索引。

【讨论】:

    【解决方案3】:

    事件发生后很长一段时间对 OP 有很大用处,上述所有加快查询的建议都是完全合适的,但我想知道为什么没有人对 EXPLAIN 的输出发表评论。具体来说,为什么选择电子邮件索引以及它与用户表中电子邮件列的定义的关系。

    优化器选择了 email 列的索引,大概是因为它包含在 where 子句中。此索引的 key_len 相对较长,并且考虑到 auto_increment 值,它是一个相当大的表,因此该索引的内存需求将比选择 id 列(4 个字节对 303 个字节)大得多。 email 列是 NULLABLE,但默认为空字符串,因此,除非应用程序显式设置 NULL,否则无论如何您都不会在此列中找到任何 NULL。在给定 UNIQUE 约束的情况下,您也不会找到多个具有默认值的记录。列 DEFAULT 和 UNIQUE 约束似乎完全相互矛盾。

    鉴于上述情况,以及我们只希望查询中的计数这一事实,我想知道 where 子句的电子邮件部分是否有任何目的,而不是在每个值与 NULL 进行比较时减慢查询速度。没有它,优化器可能会选择主键并做得更好。更好的是完全忽略用户表并根据 Gordon Linoff 突出显示的 blog_user 上的覆盖索引进行计数的查询。

    这里还有一个索引问题值得一提:

    在用户表上

     UNIQUE KEY `id` (`id`) USING BTREE,
    

    是多余的,因为 id 是主键,因此根据定义是唯一的。

    【讨论】:

      【解决方案4】:

      要回答您的最后一个问题, 问题是为什么 MyISAM 执行命令这么慢? MyISAM 取决于您的硬盘驱动器的速度, 读取数据后,INNODB 以 RAM 的速度运行。第一次运行查询可能是加载数据,第二次及以后将避免硬盘驱动器,直到内存老化。

      【讨论】:

        猜你喜欢
        • 2012-07-20
        • 2010-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-01-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多