MariaDB 5.5.68,防止有毒选择



我知道这个MariaDB版本5.5.68确实过时了,但我必须使用这个旧版本一段时间。

有没有办法防止有毒的选择,可能会阻塞MyISAM表更长的时间(分钟)?问题是,select在整个MyISAM表上创建了一个READ BLOCK,并等待进一步的插入,直到它们全部消失。因此,长期运行的选择开始阻塞系统。

举个例子表:

CREATE TABLE `tbllog` (
`LOGID` bigint unsigned NOT NULL auto_increment,
`LOGSOURCE` smallint unsigned default NULL,
`USERID` int unsigned default NULL,
`LOGDATE` datetime default NULL,
`SUBPROVIDERID` int unsigned default NULL,
`ACTIONID` smallint unsigned default NULL,
`COMMENT` varchar(255) default NULL,
PRIMARY KEY (`LOGID`),
KEY `idx_LogDate` (`LOGDATE`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

以下选择可以正常工作,直到表中的条目少于100万(客户设置日期范围):

SELECT * 
FROM tbllog 
WHERE logdate BETWEEN '2021-01-01 00:00:00' AND '2022-10-25 00:00:00' 
AND subproviderid=1 
ORDER BY logid 
LIMIT 500;

但如果表中有1000万个或更多的条目,它就会变得有毒。然后它开始运行几分钟,消耗大量内存,并开始阻止应用程序。

这是一个查询计划,表中约有6000000个条目:

+------+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
| id   | select_type | table  | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+------+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
|    1 | SIMPLE      | tbllog | index | idx_LogDate   | PRIMARY | 8       | NULL |  624 | Using where |
+------+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

问题是,在执行死刑之前,我需要知道这是否有毒。因此,也许我可以警告用户,这可能会阻塞系统一段时间,甚至拒绝执行。

我知道InnoDB可能没有这个问题,但我还不知道交换机的缺点,我认为现在最好留下来。

我之前尝试过做一个简单的SELECT COUNT(*) FROM tbllog WHERE logdate BETWEEN '2021-01-01 00:00:00' AND '2022-10-25 00:00:00' AND subproviderid=1(删除LIMIT和ORDERBY),但它并没有比实际查询快多少,而且在最坏的情况下会产生双倍的负载。

我还考虑了一个worker线程(就像这里提到的)。但这对整个系统来说也是一个相关的变化。我认为InnoDB的影响会更小。

对这个问题有什么想法吗?

EXPLAIN报告显示它正在对主键索引进行索引扫描。我认为这是因为日期范围太宽,所以优化器认为使用索引而不是简单地读取整个表并没有多大帮助。通过对主键(logid)进行索引扫描,优化器至少可以确保按ORDERBY子句中请求的顺序读取行,因此可以跳过排序。

如果我测试您的查询(我创建了表并用1M行随机数据填充它),但让它忽略主键索引,我会得到以下EXPLAIN报告:

mysql> explain SELECT *  FROM tbllog IGNORE INDEX(PRIMARY) WHERE logdate BETWEEN '2021-01-01 00:00:00' AND '2022-10-25 00:
+----+-------------+--------+------------+-------+---------------+-------------+---------+------+--------+----------+----------------------------------------------------+
| id | select_type | table  | partitions | type  | possible_keys | key         | key_len | ref  | rows   | filtered | Extra                                              |
+----+-------------+--------+------------+-------+---------------+-------------+---------+------+--------+----------+----------------------------------------------------+
|  1 | SIMPLE      | tbllog | NULL       | range | idx_LogDate   | idx_LogDate | 6       | NULL | 271471 |    10.00 | Using index condition; Using where; Using filesort |
+----+-------------+--------+------------+-------+---------------+-------------+---------+------+--------+----------+----------------------------------------------------+

这使得它使用logdate上的索引,因此它根据日期范围条件匹配的比例检查更少的行。但是,在应用LIMIT之前,必须对结果行进行排序(Extra列中的"Using filesort")。

如果你的日期范围涵盖了整张桌子,那么这一点也无济于事。事实上,情况会更糟,因为它将通过logdate索引间接访问行,然后必须对行进行排序。只有当查询中的日期范围与表的一小部分匹配时,此解决方案才有帮助。

一个稍微好一点的索引是(subproviderid, logdate)上的复合索引。

mysql> alter table tbllog add index (subproviderid, logdate);
mysql> explain SELECT *  FROM tbllog IGNORE INDEX(PRIMARY) WHERE logdate BETWEEN '2021-01-01 00:00:00' AND '2022-10-25 00:00:00'  AND subproviderid=1  ORDER BY logid  LIMIT 500;
+----+-------------+--------+------------+-------+---------------------------+---------------+---------+------+-------+----------+---------------------------------------+
| id | select_type | table  | partitions | type  | possible_keys             | key           | key_len | ref  | rows  | filtered | Extra                                 |
+----+-------------+--------+------------+-------+---------------------------+---------------+---------+------+-------+----------+---------------------------------------+
|  1 | SIMPLE      | tbllog | NULL       | range | idx_LogDate,SUBPROVIDERID | SUBPROVIDERID | 11      | NULL | 12767 |   100.00 | Using index condition; Using filesort |
+----+-------------+--------+------------+-------+---------------------------+---------------+---------+------+-------+----------+---------------------------------------+

在我的测试中,这有助于所检查行的估计从271471下降到12767,因为它们受到子供应商的限制,然后受到logdate的限制。这有多有效取决于匹配subproviderid=1的频率。如果这与几乎所有的行都匹配,那么这将没有任何帮助。如果子提供程序有许多不同的值,并且每个值都有一小部分行,那么将其添加到索引中会有更多帮助。

在我的测试中,我假设有20个频率相同的子提供程序的不同值。也就是说,我的随机数据在每行上插入round(rand()*20)作为子提供程序的值。因此,在我的测试中,添加子提供程序会导致1/20的检查行。

若要选择索引中列出的列的顺序,在相等条件中引用的列必须列在范围条件中引用列之前。

无法获得查询运行时的预测。这不是优化器可以预测的。您应该阻止用户请求与表的大部分内容匹配的日期范围。

对于此

WHERE logdate BETWEEN '2021-01-01 00:00:00' AND '2022-10-25 00:00:00' 
AND subproviderid=1 
ORDER BY logid 

加上这两个,希望优化器选择更好的:

INDEX(subproviderid, logdate, logid)
INDEX(subproviderid, logid)

更好的做法是也改为这个(假设它对你的目的是"等效的"):

ORDER BY logdate, logid 

那么第一个索引可能会很好地工作。

你真的应该换成InnoDB。(注意:表的大小可能会增加三倍。)对于InnoDB,将有另一个索引选项。而且,有了更新的版本,你可以做";即时";索引添加。同时,MyISAM将花费大量时间添加这些索引。

尝试专门为查询创建多列索引。

CREATE INDEX sub_date_logid ON tbllog (subproviderid, logdate, logid);

此索引应直接满足查询中的WHERE筛选器。然后它应该按logid的顺序显示行,这样ORDER BY ... LIMIT子句就不必对整个表进行排序。这将有助于长期死亡的MariaDB 5.5与MyISAM?很难说。

如果不能解决性能问题,请保留多列索引,并尝试对logid值而不是所有行执行ORDER BY...LIMIT

SELECT * 
FROM tbllog 
WHERE logid IN (
SELECT logid
FROM tbllog
WHERE logdate BETWEEN '2021-01-01 00:00:00' AND '2022-10-25 00:00:00' 
AND subproviderid=1 
ORDER BY logid 
LIMIT 500 )
ORDER BY logid;

这可以加快速度,因为它只允许MariaDB对logid值进行排序,以找到它想要的值。然后,外部查询只获取结果集所需的500行。要排序的数据更少=更快。

其中一个选项虽然是外部选项,但将使用ProxySQL。它有能力塑造交通。您可以创建规则来决定如何处理与其匹配的查询。例如,您可以创建一个查询规则来检查查询是否正在访问给定的表(您可以使用正则表达式来匹配查询),例如,阻止该查询或在执行中引入延迟。

另一种选择是使用pt-kill。这是Percona工具包中的一个脚本,它旨在终止查询。您可以定义要终止的查询(通过正则表达式、运行时间或其他方式进行匹配)。

话虽如此,如果SELECTs可以通过重写或添加适当的索引来优化,那么这可能是最好的选择。

相关内容

  • 没有找到相关文章

最新更新