所以我有两个表,我需要能够获得计数。其中一个包含内容,另一个包含它与类别表之间的关系。这是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)
答案 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
我也想在第二个表上获得“使用索引”,所以我需要按顺序使用(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行。
我尝试了与上面相同的覆盖索引解决方案,但使用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秒。
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;
我尝试了@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提出的解决方案,以维护一个包含每个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_en
或content_page_categories
中插入,删除或更新记录时,都应更新page_active_category
中的相应记录。
这可以通过content_en
和content_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