MySQL左加入分组 - 索引优化
我试图优化一个涉及两个表的左连接,但是我无法让我的头绕着可能的索引加速事情。 表1包含2171289行:MySQL左加入分组 - 索引优化
text_metadata_for_nzcorpus | CREATE TABLE `text_metadata_for_nzcorpus` (
`text_id` varchar(255) NOT NULL,
`newspaper` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`year` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`month` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`day` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`section` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`subsection` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`topics` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`words` int(11) NOT NULL DEFAULT '0',
`cqp_begin` bigint(20) unsigned NOT NULL DEFAULT '0',
`cqp_end` bigint(20) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`text_id`),
KEY `newspaper` (`newspaper`),
KEY `year` (`year`),
KEY `month` (`month`),
KEY `day` (`day`),
KEY `section` (`section`),
KEY `subsection` (`subsection`),
KEY `topics` (`topics`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
第二个表只包含8584行:
db_dist_fb8ddyk760 | CREATE TABLE `db_dist_fb8ddyk760` (
`text_id` varchar(255) COLLATE utf8_bin DEFAULT NULL,
`beginPosition` int(11) DEFAULT NULL,
`endPosition` int(11) DEFAULT NULL,
`refnumber` mediumint(9) NOT NULL AUTO_INCREMENT,
KEY `refnumber` (`refnumber`),
KEY `text_id` (`text_id`)
) ENGINE=InnoDB AUTO_INCREMENT=16384 DEFAULT CHARSET=utf8 COLLATE=utf8_bin |
我需要运行以下类型的查询:
SELECT md.day as handle, count(db.text_id) as hits,
count(distinct db.text_id) as files FROM text_metadata_for_nzcorpus as md
LEFT JOIN db_dist_fb8ddyk760 as db on md.text_id = db.text_id
GROUP BY md.day;
目前这需要更多处理时间超过5秒。由于这是我在网页上显示输出之前需要运行的很多查询中的一种,如果可能的话,我希望加快速度。这里是“解释”的输出:
+----+-------------+-------+-------+---------------+---------+---------+----------------------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+----------------------+---------+--------------------------+
| 1 | SIMPLE | md | index | day | day | 768 | NULL | 2452080 | Using index |
| 1 | SIMPLE | db | ref | text_id | text_id | 768 | cqpweb_db.md.text_id | 1 | Using where; Using index |
+----+-------------+-------+-------+---------------+---------+---------+----------------------+---------+--------------------------+
任何有帮助的建议,将不胜感激。 (我不是系统的开发人员,我不负责代码本身 - 但如果事情可以改进,我想为程序员提供输入...)
非常感谢! Sebastian
您的EXPLAIN报告显示您已经在两个表中使用索引,并且您没有为GROUP BY使用临时表,并且两个表都使用覆盖索引(“使用索引”)。
一些其他的事情,你除了可以创建索引做:
- 定义db_dist_fb8ddyk760.text_id为
NOT NULL
。这可能会消除“使用哪里”笔记,这意味着它必须评估表达式作为搜索的一部分。这可能会稍微更有效率。 - 将db_dist_fb8ddyk760.text_id定义为该表的PRIMARY KEY,如果这样做合理 - 换句话说,如果text_id在该表中是唯一的。这样,“type:ref”将变成“type:eq_ref”,这意味着一个独特的密钥查找,这更有效一些。但是,如果此表需要为每个text_id记录多个匹配,当然会忽略此建议。
- 将您的
innodb_buffer_pool_size
增加得足够多,以便索引可以缓存在内存中。如果查询只从缓冲池读取索引页,则可以获得更好的性能和更少的磁盘I/O。 - 利用MySQL Query Cache,所以如果您再次运行相同的查询,它将重用先前查询的结果。但是,如果这些表中的数据更改频率比执行查询更频繁,则查询缓存可能没什么用处。
- 考虑将结果缓存在应用程序内存或memcached或其他东西中。
回复您的评论:
顺便说一句,表db_dist_fb8ddyk760很可能只有一次或两次,然后丢弃使用。
那你为什么要将它存储在持久数据库中呢?
考虑使用像Redis一样的内存中键/值存储。使每个键对应一天,并且每个值都是包含点击次数和不同text_id集合的结构。这基本上是制作一个汇总表(您也可以在SQL中完成),但Redis是内存中的。
请勿盲目使用VARCHAR(255)
。使用对数据有意义的数据类型。其中许多列听起来像数字,而不是字符串。
假设年+日+日只是DATE
的一部分,请使用数据类型为DATE
的单列。然后,使用DAY(date_col)
提取日期。
每个InnoDB表应该有一个PRIMARY KEY
。也许组合(text_id, beginPosition)
是独一无二的,可能是PK?
每一列都是NULL
??我对此表示怀疑。让他们NOT NULL
除非你有一个NULL
的原因。
refnumber
是AUTO_INCREMENT
,但不是PRIMARY KEY
?是什么赋予了?
进行上述更改将有助于某些。但是,所述的查询注定要扫描整个2M行表并进入另一个表。事情可以完成。但是他们将涉及构建和维护摘要表。
完全同意有一个汇总表...即使它是预先汇总在一个特定的一天结束时,然后它只完成一次,他们可以联合只为条目最新的一天。 – DRapp
感谢你 - 一些评论:我理解你对数字而不是VARCHAR所说的话 - 但该表是一个需要灵活的系统的一部分。从一开始就不清楚在各个栏目中找到了哪些类型的数据。是的,(text_id,beginPosition)的组合是唯一的 - 将研究这个问题,还有关于列为NULL的问题。顺便说一句,表db_dist_fb8ddyk760很可能只能使用一次或两次,然后丢弃。所以我正在寻找第一次工作的优化... –
另一个问题是...“日”是每月的哪一天?或者是其他东西? (我想知道分组的目的是什么。) –
感谢您的支持。不幸的是,text_id不能成为主键。将尝试你建议的其他事情。 –
因为它被缓存,并且可以在其他用户执行相同的查询时再次使用 - 这为创建这些数据库节省了相当多的时间。没有办法事先了解多久使用一次特定数据库的用户数量。有时30个人可能会做同样的事情(这就是为什么缓存有意义),有时用户可能会导致编译一个巨大的表仅仅看一次输出......我们已经选择了持久数据库选项,因为在整体来看,这似乎是最好的折衷方案。 –
另外,“日”不是我认为你认为它是... ;-)“日”只是一个句柄,可以包含文本集合中的任何级别的注释(在这种情况下,它确实是一天的月份,即1到31之间的数字)。所有这些涉及到电子文本语料库的接口 - http://cwb.sourceforge.net/cqpweb.php - 如果您有兴趣的话。 –