我有一个请求,需要在特定时间段内多次处理,我的实现正在工作,但我的用户群每天都在增长,数据库的CPU负载和执行查询所需的时间每天都在增加
请求如下:
SELECT bill.* FROM billing bill
INNER JOIN subscriber s ON (s.subscriber_id = bill.subscriber_id)
INNER JOIN subscription sub ON(s.subscriber_id = sub.subscriber_id)
WHERE s.status = 'C'
AND bill.subscription_id = sub.subscription_id
AND sub.renewable = 1
AND (hour(sub.created_at) > 1 AND hour(sub.created_at) < 5 )
AND sub.store = 'BizaoStore'
AND (sub.purchase_token = 'myservice' or sub.purchase_token = 'myservice_wait' )
AND bill.billing_date > '2022-12-31 07:00:00' AND bill.billing_date < '2023-01-01 10:00:00'
AND (bill.billing_value = 'not_ok bizao_tobe' or bill.billing_value = 'not_ok BILL010 2' or bill.billing_value = 'not_ok BILL010' or bill.billing_value = 'not_ok BILL010 3')
AND (SELECT MAX(bill2.billing_date)
FROM billing bill2
WHERE bill2.subscriber_id = bill.subscriber_id
AND bill2.subscription_id = bill.subscription_id
AND bill2.billing_value = 'not_ok bizao_tobe')
= bill.billing_date order by sub.created_at DESC LIMIT 300;
此请求在两个不同的服务器上执行,每个服务器处理一个特定的服务。在每个服务器上,请求每分钟运行8次(大约3个小时),每一次都有以下行,并具有不同的小时:
AND (hour(sub.created_at) > 1 AND hour(sub.created_at) < 5 )
我这样做是为了将我的用户群分成8个,从而更有效地处理请求。此外,我一次只需要处理300个用户,因为我必须为每个用户调用的第三方服务器不是很稳定,有时需要很长时间才能响应
计费表计数大约50.000.000个条目,下面是列和索引的模式:
订阅者表大约为2.000.000,列方案和索引:
最后是订阅表,2.500.000行,方案和索引:
作为其他一些信息,我注意到在我的优化测试中,如果我在请求中添加这样一个事实,即我希望在特定ID上使用“billing_id”的数据,它将运行得非常快。基本上,我认为花费最多时间的是解析50.000.000行的表。
我确实(或者至少我尝试过)用时间来优化我的请求,以提高效率,但到目前为止,我有点被它卡住了。
Mysql版本为5.7.38
谢谢你的帮忙
2条答案
按热度按时间p1tboqfb1#
我认为还可以有一些改进的空间,有些更容易做到有些需要更多的努力:
1.您提到过您试图通过限制时间来提高性能,我认为这意味着
AND (hour(sub.created_at) > 1 AND hour(sub.created_at) < 5 )
,但是sub.created_at
没有索引,在数据库中created_at
和updated_at
列始终具有索引是一个好主意。1.此外,还可以在
bill.billing_date
上添加索引1.有一个查找
MAX(bill2.billing_date)
的子查询,这也可能是一个瓶颈,因为该子查询需要为上一执行阶段的每一行运行(您可以使用explain
查询检查)根据您的条件,您可以将最大字段存储在某个地方,如数据库中的列、缓存等,并在每次底层表的值改变时不断更新它。你可以用数据库触发器(有些人认为它是反模式的)或在代码中使用事务来做到这一点。1.您可以对复制副本运行查询,而不必担心是否需要花费大量时间。
总而言之,第一点可能会大大提高性能,因此其余的就没有必要了
5t7ly7z52#
我看到了一些加快此查询的机会。(参考:Markus Winand的https://use-the-index-luke.com/电子书。)
1.将correlated subquery(
SELECT MAX(bill2.billing_date)...
)替换为独立的子查询。1.试着让所有的
WHERE
子句sargable--能够利用索引。1.添加适当的索引。
独立子查询获取每个订户/订阅的最新计费日期,如下所示。此查询只需运行一次,而您的相关子查询则运行多次。
使用这个索引可以加快子查询的速度,这个索引可以让子查询满足于一个快得近乎奇迹的loose index scan。
然后像这样重写整个查询以使用它。为了提高可读性,我还重写了其他一些东西:主要是将
col = a OR col = b OR col = c
更改为col IN (a,b,c)
。我还更改了一些WHERE子句的顺序,同样是为了可读性。WHERE子句的顺序对性能没有影响。Sargability正如您所指出的,您按一天中的小时数对用户群进行细分意味着您需要此子句。
该子句将HOUR()函数应用于每个符合条件的行,因此它必须扫描所有行。慢一点。将名为
created_hour
的virtual column添加到subscription
表中。我们稍后将在该列上放置索引。然后开始使用虚拟列来划分用户。
索引复合(多列)索引是加快复杂查询速度的一种方法。列的顺序在索引中很重要。具有相等匹配的列在前面,然后是具有范围匹配的列。
首先,让我们在
subscription
表上放置一个符合查询要求的复合索引,我们将在此过程中为新的虚拟列建立索引,这将使查询计划器能够高效地查找批处理。这个索引的最后一列是
created_at
,这样可以加快ORDER BY ... LIMIT
操作的速度。接下来,让我们看看如何在主查询中使用
billing
。(我们已经添加了一个索引来帮助子查询)。您在billing_value
上匹配相等性,然后在billing_date
上匹配日期范围。因此,您需要这个索引。在
subscriber
上已经有了所需的索引。