优化大型表上的SQL查询
首先,这个问题关于MySQL 3.23.58,所以建议。
我有2个表,其定义如下:
Table A: id INT (primary), customer_id INT, offlineid INT Table B: id INT (primary), name VARCHAR(255)
现在,表A包含范围超过65k的记录,而表B包含约40条记录。除了2个主键索引外,表A中的offlineid字段上还有一个索引。每个表中都有更多字段,但它们与该查询无关(如我所见,请询问是否必要)。
首先为我提供以下查询(查询时间:〜22秒):
SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads FROM katalogbestilling_katalog a, medie b WHERE a.offlineid = b.id GROUP BY b.name
现在,medie中的每个id都关联有一个不同的名称,这意味着我们可以按id和名称进行分组。经过一些反复的测试,我对此感到满意(查询时间:〜6秒):
SELECT a.name, COUNT(*) AS orders, COUNT(DISTINCT(b.kundeid)) AS leads FROM medie a INNER JOIN katalogbestilling_katalog b ON a.id = b.offline GROUP BY b.offline;
有什么方法可以将其降低到"即时"时间(最坏情况下最多1秒)吗?我在offlineid上添加了索引,但是除此之外,还对查询进行了重新安排,我不知所措。 EXPLAIN查询向我显示查询正在使用fileshort(原始查询也使用了临时表)。欢迎所有建议!
解决方案
我要猜测,主要问题是我们使用的是MySQL的旧版本。也许MySQL 3不喜欢COUNT(DISTINCT())。
另外,它可能只是系统性能。你有多少内存?
不过,MySQL 3确实很老。我至少将测试系统放在一起,看看是否有较新的版本可以更快地运行该查询。
kundeid如何定义?查看两个表的完整架构(由MySQL生成,即带有索引)以及上述查询的EXPLAIN的输出将很有帮助。
调试此错误并找出瓶颈的最简单方法是开始从查询中一个一个地删除字段,并测量运行所需的时间(请记住在运行每个查询之前运行RESET QUERY CACHE)。在某些时候,我们会发现执行时间大大减少,然后我们就确定了瓶颈。例如:
SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads FROM katalogbestilling_katalog a, medie b WHERE a.offlineid = b.id GROUP BY b.name
可能会变成
SELECT b.name, COUNT(DISTINCT(a.kundeid)) AS leads FROM katalogbestilling_katalog a, medie b WHERE a.offlineid = b.id GROUP BY b.name
消除"订单"成为瓶颈的可能性,或者
SELECT b.name, COUNT(*) AS orders FROM katalogbestilling_katalog a, medie b WHERE a.offlineid = b.id GROUP BY b.name
消除等式中的"线索"。这将引导我们朝正确的方向前进。
更新:我不建议从最终查询中删除任何数据。寻找瓶颈时,只需删除它们以减少变量的数量即可。根据评论,我了解
SELECT b.name FROM katalogbestilling_katalog a, medie b WHERE a.offlineid = b.id GROUP BY b.name
仍然表现不佳?显然,这意味着或者是未优化的联接,或者是分组依据(我们可以通过用JOIN删除分组来测试,这仍然很慢,在这种情况下,这就是我们需要解决的问题,否则就不会解决)在这种情况下,显然是GROUP BY。你可以发布的输出
EXPLAIN SELECT b.name FROM katalogbestilling_katalog a, medie b WHERE a.offlineid = b.id GROUP BY b.name
以及表模式(使其更易于调试)?
更新#2
也有可能所有索引都正确创建,但是在最大内存使用方面,或者在那些方面有强迫其使用磁盘排序的问题时,mysql安装配置错误。
如果删除内部联接并将其替换为嵌套的select语句,同时删除count(*)并将其替换为PK,则性能可能会有所提高。
SELECT a.name,COUNT(*)个AS订单,COUNT(DISTINCT(b.kundeid))个AS潜在客户 从medie aINNER JOIN katalogbestilling_katalog b打开a.id = b.offline GROUP BY b.offline;
将是
SELECT a.name, COUNT(a.id)个AS订单, (从katalogbestilling_katalog b中选择COUNT(kundeid)条b.offline = a.id)作为销售线索 从medie a;
不幸的是,mysql 3不支持子查询。我怀疑旧版本通常是导致性能下降的原因。
好吧,如果查询经常运行以保证开销,请在表A上创建一个索引,其中包含查询中使用的字段。然后,可以从索引中读取所有结果,而不必扫描表。
就是说,我的所有经验都是基于MSSQL的,因此可能行不通。
第二个查询很好,并且65k + 40k行不是很大:)
在katalogbestilling_katalog.offline列上放置一个新索引,它将为我们更快地运行。
这需要多长时间:
SELECT fieldlist FROM A SELECT fieldlist FROM B
如果mysql在连接上运行得太慢,也许最好通过单表扫描取出数据,然后将数据缝到db之外。 65k记录的确不是很多。
我们可以尝试确保在每个表上定义了覆盖索引。覆盖索引只是一个索引,在索引中包含选择中或者联接中使用的每个列。这样,引擎仅需读取索引条目,而不必也进行相应的行查找即可获取未包括在索引中的任何请求的列。我已经在Oracle和MS SqlServer中成功使用了此技术。
查看查询,我们可以尝试:
medie.id,medie.name的一个索引
katalogbestilling_katalog.offlineid的一个索引,katalogbestilling_katalog.kundeid
应按这些顺序为索引定义列。不管是否可以使用索引,都会有所不同。
更多信息在这里:
覆盖指数信息
尝试向(offlineid,kundeid)添加索引
我在katalog中添加了180,000个BS行,在medie中添加了30,000个BS行(其中katalog offlineid对应于medie id,并带有一些重叠的kundeid以确保区分计数有效)。请注意,这是在mysql 5上进行的,因此,如果我们没有类似的结果,则mysql 3可能是罪魁祸首,但是从我的回忆中,mysql 3应该能够很好地解决这一问题。
我的桌子:
CREATE TABLE `katalogbestilling_katalog` ( `id` int(11) NOT NULL auto_increment, `offlineid` int(11) NOT NULL, `kundeid` int(11) NOT NULL, PRIMARY KEY (`id`), KEY `offline_id` (`offlineid`,`kundeid`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=60001 ; CREATE TABLE `medie` ( `id` int(11) NOT NULL auto_increment, `name` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=30001 ;
我的查询:
SELECT b.name, COUNT(*) AS orders, COUNT(DISTINCT(a.kundeid)) AS leads FROM medie b INNER JOIN katalogbestilling_katalog a ON b.id = a.offlineid GROUP BY a.offlineid LIMIT 0 , 30 "Showing rows 0 - 29 (30,000 total, Query took 0.0018 sec)"
并解释:
id: 1 select_type: SIMPLE table: a type: index possible_keys: NULL key: offline_id key_len: 8 ref: NULL rows: 180000 Extra: Using index id: 1 select_type: SIMPLE table: b type: eq_ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: test.a.offlineid rows: 1 Extra:
尝试优化服务器本身。有关最重要的变量,请参见Peter Zaitsev的这篇文章。有些是InnoDB专用的,而另一些是针对MyISAM的。我们没有提到在这种情况下我们所使用的引擎可能与之相关(例如,在MyISAM中,count(*)比在InnoDB中要快得多)。
这是同一博客的另一篇文章,以及MySQL Forge的一篇文章