mysql在连接查询中慢计数

mbo*_*las 10 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;
Run Code Online (Sandbox Code Playgroud)

而对于另一张桌子

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;
Run Code Online (Sandbox Code Playgroud)

我正在运行的查询是:

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 ;
Run Code Online (Sandbox Code Playgroud)

两个表都有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

SELECT COUNT( *) FROM content_page_categories INNER JOIN content_en ON id=itemid AND catid = 1 AND active = 1 WHERE 1

有任何想法吗?

[编辑]

在这里上传了这些表的样本数据

解释结果:

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)
Run Code Online (Sandbox Code Playgroud)

Bil*_*win 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
Run Code Online (Sandbox Code Playgroud)
  • 这在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
Run Code Online (Sandbox Code Playgroud)

rows由EXPLAIN报告是它有多少工作要采取执行查询的一个重要指标.请注意rows,上面的EXPLAIN只有71k,比你先扫描content_en表时得到的125k行要小得多.

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

将表转换为InnoDB

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

mysql> ALTER TABLE content_en ENGINE=InnoDB;
mysql> ALTER TABLE content_page_categories ENGINE=InnoDB;
Run Code Online (Sandbox Code Playgroud)

这与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;
Run Code Online (Sandbox Code Playgroud)
  • 这在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;
Run Code Online (Sandbox Code Playgroud)

这在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
Run Code Online (Sandbox Code Playgroud)

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

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


Qua*_*noi 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;
Run Code Online (Sandbox Code Playgroud)

然后填充它:

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

现在,您将每一次,删除或更新任何记录content_en或者content_page_categories,你应该更新相应的记录page_active_category.

这是可行的与双方两个简单的触发器content_encontent_page_categories.

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

SELECT  cnt
FROM    page_active_category
WHERE   active = 1
        AND catid = 1
Run Code Online (Sandbox Code Playgroud)

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