10

所以我有两张桌子需要计算。其中一个保存内容,另一个保存内容与类别表之间的关系。这是 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

有任何想法吗?

[编辑]

我在这里上传了这些表的示例数据

解释的结果:

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)
4

5 回答 5

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是执行查询需要多少工作的重要指标。请注意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;

这有相同的 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 秒内执行。所以这是一个数量级的最佳解决方案,如果您可以维护具有聚合计数的表。

从中可以看出,不同类型的非规范化(包括汇总表)在性能方面是一个非常强大的工具,尽管它有缺点,因为维护冗余数据可能不方便,并使您的应用程序更加复杂。

于 2013-07-09T17:53:16.703 回答
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_en这可以通过和上的两个简单触发器来实现content_page_categories

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

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

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

于 2013-07-09T17:32:59.320 回答
1

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

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

必须加入每个 content_en 记录只是为了读取“活动”标志确实是减慢此查询的原因。

我建议将“活动”添加到 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/

于 2013-07-09T16:57:55.123 回答
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_JoinWhen to use STRAIGHT_JOIN with MySQL

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

于 2013-07-09T17:37:46.207 回答
0

为了加快对 mysql 连接的计数,请使用子查询。

例如使用 placeCount 获取城市

城市表

身份证标题……

放置表

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
于 2019-04-12T15:01:27.430 回答