我有以下数据库(简化):
CREATE TABLE `tracking` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`manufacture` varchar(100) NOT NULL,
`date_last_activity` datetime NOT NULL,
`date_created` datetime NOT NULL,
`date_updated` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `manufacture` (`manufacture`),
KEY `manufacture_date_last_activity` (`manufacture`, `date_last_activity`),
KEY `date_last_activity` (`date_last_activity`),
) ENGINE=InnoDB AUTO_INCREMENT=401353 DEFAULT CHARSET=utf8
CREATE TABLE `tracking_items` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tracking_id` int(11) NOT NULL,
`tracking_object_id` varchar(100) NOT NULL,
`tracking_type` int(11) NOT NULL COMMENT 'Its used to specify the type of each item, e.g. car, bike, etc',
`date_created` datetime NOT NULL,
`date_updated` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `tracking_id` (`tracking_id`),
KEY `tracking_object_id` (`tracking_object_id`),
KEY `tracking_id_tracking_object_id` (`tracking_id`,`tracking_object_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1299995 DEFAULT CHARSET=utf8
CREATE TABLE `cars` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`car_id` varchar(255) NOT NULL COMMENT 'It must be VARCHAR, because the data is coming from external source.',
`manufacture` varchar(255) NOT NULL,
`car_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`date_order` datetime NOT NULL,
`date_created` datetime NOT NULL,
`date_updated` datetime NOT NULL,
`deleted` tinyint(4) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `car_id` (`car_id`),
KEY `sort_field` (`date_order`)
) ENGINE=InnoDB AUTO_INCREMENT=150000025 DEFAULT CHARSET=utf8
这是我的“有问题”查询,运行速度非常慢。
SELECT sql_no_cache `t`.*,
count(`t`.`id`) AS `cnt_filtered_items`
FROM `tracking` AS `t`
INNER JOIN `tracking_items` AS `ti` ON (`ti`.`tracking_id` = `t`.`id`)
LEFT JOIN `cars` AS `c` ON (`c`.`car_id` = `ti`.`tracking_object_id`
AND `ti`.`tracking_type` = 1)
LEFT JOIN `bikes` AS `b` ON (`b`.`bike_id` = `ti`.`tracking_object_id`
AND `ti`.`tracking_type` = 2)
LEFT JOIN `trucks` AS `tr` ON (`tr`.`truck_id` = `ti`.`tracking_object_id`
AND `ti`.`tracking_type` = 3)
WHERE (`t`.`manufacture` IN('1256703406078',
'9600048390403',
'1533405067830'))
AND (`c`.`car_text` LIKE '%europe%'
OR `b`.`bike_text` LIKE '%europe%'
OR `tr`.`truck_text` LIKE '%europe%')
GROUP BY `t`.`id`
ORDER BY `t`.`date_last_activity` ASC,
`t`.`id` ASC
LIMIT 15
这是 EXPLAIN
对于上述查询:
+----+-------------+-------+--------+-----------------------------------------------------------------------+-------------+---------+-----------------------------+---------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | extra |
+----+-------------+-------+--------+-----------------------------------------------------------------------+-------------+---------+-----------------------------+---------+----------------------------------------------+
| 1 | SIMPLE | t | index | PRIMARY,manufacture,manufacture_date_last_activity,date_last_activity | PRIMARY | 4 | NULL | 400,000 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | ti | ref | tracking_id,tracking_object_id,tracking_id_tracking_object_id | tracking_id | 4 | table.t.id | 1 | NULL |
| 1 | SIMPLE | c | eq_ref | car_id | car_id | 767 | table.ti.tracking_object_id | 1 | Using where |
| 1 | SIMPLE | b | eq_ref | bike_id | bike_id | 767 | table.ti.tracking_object_id | 1 | Using where |
| 1 | SIMPLE | t | eq_ref | truck_id | truck_id | 767 | table.ti.tracking_object_id | 1 | Using where |
+----+-------------+-------+--------+-----------------------------------------------------------------------+-------------+---------+-----------------------------+---------+----------------------------------------------+
此查询要解决的问题是什么?
基本上,我需要找到所有的记录 tracking
可能与中的记录关联的表 tracking_items
(1:n)每个记录 tracking_items
可能与左联接表中的记录相关联。过滤条件是查询的关键部分。
上面的问题是什么?
当有 order by
以及 group by
子句查询运行非常慢,例如,要完成上述配置需要10-15秒。但是,如果省略这些子句中的任何一个,查询运行得非常快(~0.2秒)。
我已经试过了?
我试着用 FULLTEXT
指数,但并没有太大的帮助,因为结果的评估 LIKE
州际网络被 JOINs
使用索引。
我试着用 WHERE EXISTS (...)
以查找中是否有记录 left
加入了表格,但不幸的是没有任何运气。
关于这些表之间关系的一些注解:
tracking -> tracking_items (1:n)
tracking_items -> cars (1:1)
tracking_items -> bikes (1:1)
tracking_items -> trucks (1:1)
所以,我在寻找一种优化查询的方法。
6条答案
按热度按时间2w3kk1z51#
那就试试吧
看看能不能给你一辆合适的15辆车。
如果看起来没问题,那么将三者结合起来:
注意,内部
SELECTs
只提供t.id
,不是t.*
.需要其他索引:
索引
当你有
INDEX(a,b)
,你不需要INDEX(a)
. (这对有问题的查询没有帮助,但有助于节省磁盘空间和内存INSERT
性能。)当我看到
PRIMARY KEY(id), UNIQUE(x)
,我想找个好理由id
换成PRIMARY KEY(x)
. 除非在模式的“简化”中有什么重要的东西,否则这样的改变会有所帮助。是 啊,car_id
是庞大的,等等,但它是一个大表,额外的查找(从索引btree到数据btree)是有害的,等等。我认为不大可能
KEY
排序字段(date_order)
永远不会被使用。要么放弃它(节省一些gb),要么以某种有用的方式合并它。让我们看看您认为有用的查询(同样,这是一个与这个问题没有直接关系的建议。)重新评论
我对m做了一些实质性的改变
imzjd6km2#
billkarwin建议,如果使用前导列为
manufacture
. 我赞成这个建议。特别是如果这很有选择性的话。我还注意到我们正在做一个
GROUP BY t.id
,在哪里id
是表的主键。没有任何表中的列,除了
tracking
在SELECT
列表。这表明我们真正感兴趣的是返回
t
,而不是由于多个外部联接而创建重复项。好像是
COUNT()
如果聚合中有多个匹配行,则聚合有可能返回膨胀的计数tracking_item
以及bikes
,cars
,trucks
. 如果有三个匹配的行来自汽车,四个匹配的行来自自行车。。。count()聚合将返回值12,而不是7(或者数据中有某种保证,这样就不会有多个匹配行。)如果
manufacture
是非常有选择性的,它从tracking
,如果查询可以使用索引。。。因为我们不从任何表返回任何列,除了
tracking
,除了计数或相关项目。。。我会尝试在select列表中测试相关的子查询,以获得计数,并使用having子句过滤掉零计数行。
像这样:
我们希望查询能够有效地利用
tracking
带前导列manufacture
.在街上
tracking_items
表中,我们需要一个前导列为type
以及tracking_id
. 包括tracking_object_id
在这种情况下,索引意味着可以通过索引满足查询,而无需访问底层页面。对于
cars
,bikes
以及trucks
查询表应使用前导列为的索引car_id
,bike_id
,和truck_id
分别。无法绕过扫描car_text
,bike_text
,truck_text
匹配字符串的列。。。我们所能做的就是缩小需要执行检查的行数。这种方法(只是
tracking
表)应该不需要GROUP BY
,识别和折叠重复行所需的工作。但是这种用相关子查询替换连接的方法最适合于外部查询返回少量行的查询。对于外部查询处理的每一行,都会执行这些子查询。这些子查询必须有合适的索引可用。即使那些调整,仍然有潜力可怕的表现为大集。
这仍然留给我们一个“usingfilesort”操作
ORDER BY
.如果相关项的计数应该是乘法的乘积,而不是加法的乘积,那么我们可以调整查询来实现这一点(我们必须处理零的返回,having子句中的条件需要更改。)
如果不需要返回相关项的count(),那么我会尝试将相关子查询从select列表向下移动到
EXISTS
中的 predicateWHERE
条款。附加说明:附议rick james关于索引的评论。。。似乎定义了冗余索引。即
单例列上的索引不是必需的,因为还有另一个索引将该列作为前导列。
任何能够有效利用
manufacture
索引将能够有效地利用manufacture_date_last_activity
索引。也就是说manufacture
索引可能会被删除。这同样适用于
tracking_items
表和这两个索引:这个
tracking_id
索引可能会被删除,因为它是多余的。对于上面的查询,我建议添加一个覆盖索引:
-或者-至少是两列前导的非覆盖索引:
d6kp6zgx3#
当有
order by
以及group by
子句查询运行非常慢,例如,要完成上述配置需要10-15秒。但是,如果省略这些子句中的任何一个,查询运行得非常快(~0.2秒)。这很有趣。。。一般来说,我所知道的最好的优化技术是充分利用临时表,听起来它在这里会非常有效。因此,首先要创建临时表:
然后查询您需要的结果:
pinkon5k4#
explain显示您正在执行索引扫描(“index”在
type
列)。索引扫描的成本与表扫描相当,尤其是当扫描的索引是主索引时。这个
rows
列还显示这个索引扫描正在检查>355k行(因为这个数字只是一个粗略的估计,实际上它正在检查所有的400k行)。有索引吗
t.manufacture
? 我看到两个索引在possible keys
这可能包括该列(我不能完全根据索引的名称来确定),但由于某些原因,优化器没有使用它们。可能您搜索的值集与表中的每一行都匹配。如果
manufacture
值是为了匹配表的一个子集,那么您可能需要给优化器一个提示,使其使用最佳索引。https://dev.mysql.com/doc/refman/5.6/en/index-hints.html使用
LIKE '%word%'
模式匹配不能使用索引,必须计算每一行的模式匹配。请看我的演示文稿,全文搜索throwdown。你房间里有多少东西
IN(...)
列表?mysql有时会遇到很长列表的问题。看到了吗https://dev.mysql.com/doc/refman/5.6/en/range-optimization.html#equality-范围优化p、 当你问一个查询优化问题时,你应该总是包括
SHOW CREATE TABLE
查询中引用的每个表的输出,所以回答的人不必猜测您当前有哪些索引、数据类型和约束。k3fezbri5#
如果我的猜测是正确的
cars
,bikes
,和trucks
相互独立(即,特定的预聚合结果将只包含其中一个的数据)。最好合并三个更简单的子查询(每个子查询一个)。虽然你不能做很多索引明智的喜欢涉及领先的通配符;将其拆分为联合查询可以避免计算
p.fb_message LIKE '%Europe%' OR p.fb_from_name LIKE '%Europe%
对于所有的cars
以及bikes
火柴,还有c
所有项目的条件b
以及t
火柴,等等。k75qkfdt6#
首先:您的查询对字符串内容进行了假设,但它不应该这样做。什么可能
car_text like '%europe%'
说明?像这样的'Sold in Europe only'
也许 吧?或者Sold outside Europe only
? 两个可能有矛盾含义的字符串。所以如果你一旦发现europe
在字符串中,您应该能够在数据库中引入这些知识—例如使用欧洲国旗或地区代码。不管怎样,你正在展示他们的欧洲运输计数某些跟踪。所以选择轨道,选择运输计数。您可以在
SELECT
条款或在您的FROM
条款。中的子查询
SELECT
条款:中的子查询
FROM
条款:索引:
跟踪(制造商、上次活动日期、id)
跟踪\u项(跟踪\u id、跟踪\u类型、跟踪\u对象\u id)
车辆(车辆文本、车辆id)
自行车(自行车文本,自行车id)
卡车(卡车文本,卡车id)
有时mysql在简单连接上比其他任何连接上都强大,所以盲目地连接运输记录可能值得一试,以后再看看是汽车、自行车还是卡车: