MySQL数据库行COUNT优化

时间:2015-09-25 10:43:02

标签: php mysql database innodb myisam

我有一个拥有大量数据的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

enter image description here

用户是MyISAM,包含id,email,name等用户数据......

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

enter image description here

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

enter image description here

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

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

  1. 当我从用户表中删除某些列时,查询的持续时间会缩短(例如每个已删除列的2秒)
  2. 当我从用户表中删除所有列时(ID和电子邮件除外),查询持续时间为0.6秒。
  3. 当我将blog_user表也更改为MyISAM时,查询的持续时间为46秒。
  4. 当我将用户表更改为InnoDB时,查询的持续时间为0.1秒。
  5. 问题是为什么MyISAM执行命令这么慢?

4 个答案:

答案 0 :(得分:1)

首先,对您的查询进行一些评论(稍微修改一下后):

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.列越少,存储记录所需的页面就越少 - 查询运行得越快。适当的索引应该更好,更一致。

答案 1 :(得分:0)

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

但它肯定与两个存储引擎之间更重要的差异之一有关:InnoDB支持外键,而myisam则不支持。外键对于连接表非常重要。

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

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

答案 2 :(得分:0)

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

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

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

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

在用户表

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

是多余的,因为id是PRIMARY KEY,因此根据定义是UNIQUE。

答案 3 :(得分:0)

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