如何解决在具有10百万行的表上使用1个联接优化查询
我正在考虑使用2个表更快地发出请求。
我有以下两个表:
表格“日志”
-
id varchar(36) PK
-
date timestamp(2)
- 更多varchar字段和一个文本字段
该表具有PHP Laravel框架所称的与其他几个对象的“多态多对多”关系,因此有第二个表“ logs_pivot”:
-
id unsigned int PK
-
log_id varchar(36) FOREIGN KEY (logs.id)
-
model_id varchar(40)
-
model_type varchar(50)
logs_pivot
中每个条目在logs
中有一个或几个条目。它们分别具有20+和10+百万行。
我们这样查询:
select * from logs
join logs_pivot on logs.id = logs_pivot.log_id
where model_id = 'some_id' and model_type = 'My\Class'
order by date desc
limit 50;
很显然,我们在model_id和model_type字段上都有一个复合索引,但是请求仍然很慢:每次都要几(数十秒)秒。
我们在date
字段上也有一个索引,但是EXPLAIN
表明这是使用的model_id_model_type
索引。
说明:
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
| 1 | SIMPLE | logs_pivot | NULL | ref | logs_pivot_model_id_model_type_index,logs_pivot_log_id_index | logs_pivot_model_id_model_type_index | 364 | const,const | 1 | 100.00 | Using temporary; Using filesort |
| 1 | SIMPLE | logs | NULL | eq_ref | PRIMARY | PRIMARY | 146 | the_db_name.logs_pivot.log_id | 1 | 100.00 | NULL |
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
在其他表中,通过在索引中包含日期字段,我能够更快地发出类似的请求。但是在那种情况下,它们在单独的表中。
当我们要访问这些数据时,它们通常需要几个小时/天。
我们的InnoDB池太小,无法将所有数据(以及所有其他表)保存在内存中,因此很可能总是在磁盘上查询数据。
我们可以怎样更快地提出请求?
理想情况下,仅使用另一个索引或通过更改索引的方式。
非常感谢!
编辑17h05:
到目前为止,谢谢大家的答复,我将尝试O Jones的建议,并以某种方式在数据透视表中包括日期字段,以便可以将其包括在索引索引中。
编辑14/10 10小时。
解决方案:
因此,我最终通过对数据透视表的id字段进行了排序来改变了请求的执行方式,确实允许将其放入索引中。
当未按日期过滤时,对总行数进行计数的请求也更改为仅在数据透视表上完成。
谢谢大家!
解决方法
我看到两个问题:
-
当表相对于RAM大小巨大时,UUID成本很高。
-
LIMIT
不能得到最佳处理,因为WHERE
子句来自一个表,而ORDER BY
列来自另一个表。也就是说,它将处理所有JOIN
,然后进行排序并最终剥离几行。
只是一个建议。使用复合索引显然是一件好事。另一个可能是按日期对ID进行资格预审,并根据您的logs_pivot表索引(model_id,model_type,log_id)来扩展索引。
如果您要查询数据,并且整个历史记录为20+百万条记录,那么数据仅处理每个给定类别的模型ID /类型最多只能有50条记录的数据才走多远。说三个月? vs说您5年的日志? (未在帖子中列出,仅作为示例)。因此,如果您可以查询日期大于3个月的最小日志ID,则该ID可以限制logs_pivot表中发生的其他事情。
类似
select
lp.*,l.date
from
logs_pivot lp
JOIN Logs l
on lp.log_id = l.id
where
model_id = 'some_id'
and model_type = 'My\Class'
and log_id >= ( select min( id )
from logs
where date >= datesub( curdate(),interval 3 month ))
order by
l.date desc
limit
50;
因此,log_id的where子句执行一次,仅返回3个月之前的ID,而不返回logs_pivot的整个历史记录。然后,您可以使用经过优化的由两部分组成的模型ID /类型的键进行查询,但是还可以跳至索引的末尾,索引键中包含的ID可以跳过所有历史记录。
您可能要包括的另一件事是一些预先汇总的表,这些表汇总了多少记录,例如每个给定模型类型/ id的每月/每年。将其用作向用户展示的预查询,然后可以将其用作下钻以进一步获得更多详细信息。预汇总表可以在所有历史资料上完成一次,因为它是静态的并且不会改变。您唯一需要不断更新的就是当前每个月的时间段,例如每晚。甚至可能更好,通过触发器,触发器每次添加一次插入记录,或者根据年/月汇总更新给定模型/类型的计数。再次,这只是一个建议,没有其他关于如何/为什么将数据呈现给最终用户的上下文。
, javascript
是一个臭名昭著的查询性能反模式。为什么?服务器将一整排长行排序,然后丢弃几乎所有长行。您的SELECT columns FROM big table ORDER BY something LIMIT small number
之一是LOB(即TEXT列)并没有帮助。
这是一种可以减少开销的方法:通过找到所需的主键集找出所需的行,然后仅获取那些行的内容。
您想要什么行?此子查询找到它们。
columns
这可以很轻松地计算出所需的行。因此,这是您需要优化的查询。
可以通过 SELECT id
FROM logs
JOIN logs_pivot
ON logs.id = logs_pivot.log_id
WHERE logs_pivot.model_id = 'some_id'
AND logs_pivot.model_type = 'My\Class'
ORDER BY logs.date DESC
LIMIT 50
上的该索引来加速
logs
和CREATE INDEX logs_date_desc ON logs (date DESC);
上的此三列复合索引
logs_pivot
此索引可能会更好,因为优化器将在CREATE INDEX logs_pivot_lookup ON logs_pivot (model_id,model_type,log_id);
上看到过滤,但在logs_pivot
上看不到过滤。因此,它将首先在logs
中查找。
或者也许
logs_pivot
尝试一个然后另一个,以查看产生更快结果的方法。 (我不确定JOIN将如何使用复合索引。)(或简单地将两者都添加,然后使用CREATE INDEX logs_pivot_lookup ON logs_pivot (log_id,model_id,model_type);
来查看使用的是哪个索引。)
然后,当您对子查询的性能感到满意(或无论如何满意)时,可以使用它来获取所需的行,像这样
EXPLAIN
这行得通,因为它排序的数据较少。 SELECT *
FROM logs
WHERE id IN (
SELECT id
FROM logs
JOIN logs_pivot
ON logs.id = logs_pivot.log_id
WHERE logs_pivot.model_id = 'some_id'
AND model_type = 'My\Class'
ORDER BY logs.date DESC
LIMIT 50
)
ORDER BY date DESC
涵盖三列的索引也有帮助。
请注意,子查询和主查询都具有ORDER BY子句,以确保返回的详细结果集符合您所需的顺序。
编辑 Darnit,曾经使用过MariaDB 10+和MySQL 8+,所以我忘记了旧的限制。试试这个吧。
logs_pivot
最后,如果您知道只关心比某些时间新的行,则可以在子查询中添加类似的内容。
SELECT *
FROM logs
JOIN (
SELECT id
FROM logs
JOIN logs_pivot
ON logs.id = logs_pivot.log_id
WHERE logs_pivot.model_id = 'some_id'
AND model_type = 'My\Class'
ORDER BY logs.date DESC
LIMIT 50
) id_set ON logs.id = id_set.id
ORDER BY date DESC
如果表中有大量历史数据,这将有很大帮助。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。