mysql在连接查询中慢计数

时间:2013-07-04 12:49:33

标签: mysql sql

所以我有两个表,我需要能够获得计数。其中一个包含内容,另一个包含它与类别表之间的关系。这是DDl:

CREATE TABLE content_en (
    id int(11) NOT NULL AUTO_INCREMENT,
    title varchar(100) DEFAULT NULL,
    uid int(11) DEFAULT NULL,
    date_added int(11) DEFAULT NULL,
    date_modified int(11) DEFAULT NULL,
    active tinyint(1) DEFAULT NULL,
    comment_count int(6) DEFAULT NULL,
    orderby tinyint(4) DEFAULT NULL,
    settings text,
    permalink varchar(255) DEFAULT NULL,
    code varchar(3) DEFAULT NULL,
    PRIMARY KEY (id),
    UNIQUE KEY id (id),
    UNIQUE KEY id_2 (id) USING BTREE,
    UNIQUE KEY combo (id,active) USING HASH,
    KEY code (code) USING BTREE
) ENGINE=MyISAM AUTO_INCREMENT=127126 DEFAULT CHARSET=utf8;

和另一个表

CREATE TABLE content_page_categories (
    catid int(11) unsigned NOT NULL,
    itemid int(10) unsigned NOT NULL,
    main tinyint(1) DEFAULT NULL,
    KEY itemid (itemid),
    KEY catid (catid),
    KEY combo (catid,itemid) USING BTREE
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

我正在运行的查询是:

SELECT count(*) 
FROM content_page_categories USE INDEX (combo) 
INNER JOIN content_en USE INDEX (combo) ON (id = itemid) 
WHERE catid = 1 AND active = 1 ;

两个表都有125k行,我无法让计数查询运行得足够快。我得到的最佳时机是0.175,这对于大量的行来说是可怕的。选择100行的速度最快为0.01。我试过这个查询的3个或4个变种,但最后的时间差不多。此外,如果我不做USE INDEX时间变慢3倍。

还尝试了以下内容: SELECT COUNT( *) FROM content_page_categories INNER JOIN content_en ON id=itemid AND catid = 1 AND active = 1 WHERE 1

和:

SELECT SQL_CALC_FOUND_ROWS catid,content_en.* FROM content_page_categories INNER JOIN content_en ON (id=itemid) WHERE catid =1 AND active = 1 LIMIT 1; SELECT FOUND_ROWS();

索引定义: content_en 0 PRIMARY 1 id A 125288 BTREE
content_en 0 id 1 id A 125288 BTREE
content_en 0 id_2 1 id A 125288 BTREE
content_en 0 combo 1 id A BTREE
content_en 0 combo 2 active A YES BTREE
content_en 1 code 1 code A 42 YES BTREE

content_page_categories 1 itemid 1 itemid A 96842 BTREE
content_page_categories 1 catid 1 catid A 10 BTREE
content_page_categories 1 combo 1 catid A 10 BTREE
content_page_categories 1 combo 2 itemid A 96842 BTREE

有什么想法吗?

[编辑]

我上传了这些表here

的示例数据

解释结果:

mysql> explain SELECT count(*) FROM  content_page_categories USE INDEX (combo) I<br>
NNER JOIN content_en USE INDEX (combo) ON (id = itemid) WHERE  catid = 1 AND act<br>
ive = 1 ;

+----+-------------+-------------------------+-------+---------------+-------+---------+--------------------------+--------+--------------------------+
| id | select_type | table                   | type  | possible_keys | key   | key_len | ref                      | rows   | Extra                    |
+----+-------------+-------------------------+-------+---------------+-------+---------+--------------------------+--------+--------------------------+
|  1 | SIMPLE      | content_en              | index | combo         | combo | 6 | NULL                     | 125288 | Using where; Using index |
|  1 | SIMPLE      | content_page_categories | ref   | combo         | combo | 8 | const,mcms.content_en.id |      1 | Using where; Using index |
+----+-------------+-------------------------+-------+---------------+-------+---------+--------------------------+--------+--------------------------+
2 rows in set (0.00 sec)

5 个答案:

答案 0 :(得分:12)

我下载了您的数据并尝试了一些实验。我在Macbook Pro上的CentOS虚拟机上运行MySQL 5.6.12。我观察到的时间可用于比较,但您的系统可能有不同的性能。

基本案例

首先我尝试了没有USE INDEX子句,因为我尽可能避免优化覆盖。在大多数情况下,像这样的简单查询应该使用正确的索引(如果可用)。在查询中对索引选项进行硬编码会使以后更难使用更好的索引。

我还使用相关名(表别名)来使查询更清晰。

mysql> EXPLAIN SELECT COUNT(*) FROM content_en AS e  
INNER JOIN content_page_categories AS c ON c.itemid = e.id 
WHERE c.catid = 1 AND e.active = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: ref
possible_keys: combo,combo2
          key: combo
      key_len: 4
          ref: const
         rows: 71198
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: e
         type: eq_ref
possible_keys: PRIMARY,combo2,combo
          key: PRIMARY
      key_len: 4
          ref: test.c.itemid
         rows: 1
        Extra: Using where
  • 这是在0.36秒内执行的。

覆盖指数

我也想在第二个表上获得“使用索引”,所以我需要按顺序使用(active,id)索引。在这种情况下,我不得不使用INDEX来说服优化器不要使用主键。

mysql> ALTER TABLE content_en ADD KEY combo2 (active, id);

mysql> explain SELECT COUNT(*) FROM content_en AS e USE INDEX (combo2) 
INNER JOIN content_page_categories AS c ON c.itemid = e.id 
WHERE c.catid = 1 AND e.active = 1\G
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: c
         type: ref
possible_keys: combo,combo2
          key: combo
      key_len: 4
          ref: const
         rows: 71198
        Extra: Using index
*************************** 2. row ***************************
           id: 1
  select_type: SIMPLE
        table: e
         type: ref
possible_keys: combo2
          key: combo2
      key_len: 6
          ref: const,test.c.itemid
         rows: 1
        Extra: Using where; Using index

EXPLAIN报告的rows是执行查询需要花费多少工作的重要指标。请注意,上面EXPLAIN中的rows仅为71k,远小于您首次扫描content_en表时获得的125k行。

  • 这在0.44秒内执行。这是意料之外的,因为通常使用覆盖索引的查询是一种改进。

将表转换为InnoDB

我尝试了与上面相同的覆盖索引解决方案,但使用InnoDB作为存储引擎。

mysql> ALTER TABLE content_en ENGINE=InnoDB;
mysql> ALTER TABLE content_page_categories ENGINE=InnoDB;

这与EXPLAIN报告相同。它花了1或2次迭代来加热缓冲池,但随后查询的性能增加了两倍。

  • 这在0.16秒内执行。

  • 我也尝试删除USE INDEX,时间略有增加,达到0.17秒。

@ Matthew使用STRAIGHT_JOIN

的解决方案
mysql> SELECT straight_join count(*) 
 FROM content_en 
 INNER JOIN content_page_categories use index (combo) 
  ON (id = itemid) 
 WHERE catid = 1 AND active = 1;
  • 执行时间为0.20 - 0.22秒。

@ bobwienholt的解决方案,非规范化

我尝试了@bobwienholt提出的解决方案,使用非规范化将active属性复制到content_page_categories表。

mysql> ALTER TABLE content_page_categories ADD COLUMN active TINYINT(1);
mysql> UPDATE content_en JOIN content_page_categories ON id = itemid 
    SET content_page_categories.active = content_en.active;
mysql> ALTER TABLE content_page_categories ADD KEY combo3 (catid,active);
mysql> SELECT COUNT(*) FROM content_page_categories WHERE catid = 1 and active = 1;

这在0.037 - 0.044秒内执行。如果您可以将冗余的active列与content_en表中的值保持同步,那么这样做会更好。

@ Quassnoi的解决方案,汇总表

我尝试了@Quassnoi提出的解决方案,以维护一个包含每个catid和活动的预计算计数的表。该表应该只有很少的行,并且查找您需要的计数是主键查找并且不需要JOIN。

mysql> CREATE TABLE page_active_category (
 active INT NOT NULL, 
 catid INT NOT NULL, 
 cnt BIGINT NOT NULL,
 PRIMARY KEY (active, catid) 
) ENGINE=InnoDB;

mysql> INSERT INTO page_active_category
 SELECT  e.active, c.catid, COUNT(*)
 FROM    content_en AS e
 JOIN    content_page_categories AS c ON c.itemid = e.id
 GROUP BY e.active, c.catid

mysql> SELECT cnt FROM page_active_category WHERE active = 1 AND catid = 1

执行0.0007 - 0.0017秒。因此,如果您可以使用聚合计数维护表,那么这是的最佳解决方案

您可以从中看到,不同类型的非规范化(包括摘要表)是一个非常强大的工具,虽然它有缺点,因为维护冗余数据可能会带来不便并使您的应用程序更加复杂。 / p>

答案 1 :(得分:5)

记录的记录太多了。

如果您想要更快的解决方案,则必须存储汇总数据。

MySQL不支持物化视图(或SQL Server术语中的索引视图),因此您需要自己创建和维护它们。

创建一个表格:

CREATE TABLE
        page_active_category
        (
        active INT NOT NULL,
        catid INT NOT NULL,
        cnt BIGINT NOT NULL,
        PRIMARY KEY
                (active, catid)
        ) ENGINE=InnoDB;

然后填充它:

INSERT
INTO    page_active_category
SELECT  active, catid, COUNT(*)
FROM    content_en
JOIN    content_page_categories
ON      itemid = id
GROUP BY
        active, catid

现在,每次在content_encontent_page_categories中插入,删除或更新记录时,都应更新page_active_category中的相应记录。

这可以通过content_encontent_page_categories上的两个简单触发器来实现。

这样,原始查询可能会被重写为仅仅:

SELECT  cnt
FROM    page_active_category
WHERE   active = 1
        AND catid = 1

这是一个主键查找,因此是即时的。

答案 2 :(得分:1)

问题是content_en中的“活动”列。显然,如果您只需要知道有多少内容记录与特定类别(活动或非活动)相关,那么您需要做的就是:

SELECT count(1)
FROM content_page_categories
WHERE catid = 1;

必须加入到每个content_en记录只是为了读取“活动”标志才真正减缓了这个查询的速度。

我建议将“active”添加到content_page_categories并将其作为content_en中相关值的副本...您可以使用触发器或代码使此列保持最新。然后,您可以将组合索引更改为:

KEY combo (catid,active,itemid)

并将您的查询重写为:

SELECT count(1)
FROM content_page_categories USE INDEX (combo)
WHERE catid = 1 AND active = 1;

另外,使用InnoDB表而不是MyISAM可能会有更好的运气。请务必调整您的InnoDB设置:http://www.mysqlperformanceblog.com/2007/11/01/innodb-performance-optimization-basics/

答案 3 :(得分:0)

对于我的数据设置,我得到的连接查询比从content_page_categories中选择的时间长约50倍。

通过对数据执行以下操作,我的性能比仅从类别表中选择要快10倍:

我使用了straight_join

    SELECT straight_join count(*) 
    FROM content_en 
    INNER JOIN content_page_categories use index (combo) 
     ON (id = itemid) 
    WHERE catid = 1 AND active = 1 ;

和下表结构(略有修改):

 CREATE TABLE `content_en` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `title` varchar(100) DEFAULT NULL,
 `uid` int(11) DEFAULT NULL,
 `date_added` int(11) DEFAULT NULL,
 `date_modified` int(11) DEFAULT NULL,
 `active` tinyint(1) DEFAULT NULL,
 `comment_count` int(6) DEFAULT NULL,
 `orderby` tinyint(4) DEFAULT NULL,
 `settings` text,
 `permalink` varchar(255) DEFAULT NULL,
 `code` varchar(3) DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `id` (`id`),
 KEY `test_con_1` (`active`) USING HASH,
 KEY `combo` (`id`,`active`) USING HASH
 ENGINE=MyISAM AUTO_INCREMENT=127126 DEFAULT CHARSET=utf8

CREATE TABLE `content_page_categories` (
`catid` int(11) unsigned NOT NULL,
`itemid` int(10) unsigned NOT NULL,
`main` tinyint(1) DEFAULT NULL,
KEY `itemid` (`itemid`),
KEY `catid` (`catid`),
KEY `test_cat_1` (`catid`) USING HASH,
KEY `test_cat_2` (`itemid`) USING HASH,
KEY `combo` (`itemid`,`catid`) USING HASH
ENGINE=MyISAM DEFAULT CHARSET=utf8

为了实现更好的效果,我认为你需要一个视图,一个扁平的结构,或者另一种类型的查找字段(如触发器在另一个表中填充一行,如另一张海报所讨论的那样)。

编辑:

我还应该指出这个关于为什么/何时要谨慎对待Straight_Join的好文章: When to use STRAIGHT_JOIN with MySQL

如果您使用它,请负责任地使用它!

答案 4 :(得分:0)

为了加快对mysql联接的依赖,请使用子查询。

例如,使用placeCount获取城市

城市表

id 标题 ......

位置表

id city_id 标题 .....

SELECT city.title,subq.count as placeCount
FROM city
       left join (
         select city_id,count(*) as count from place
         group by city_id
  ) subq
on city.id=subq.city_id