我知道这个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可以通过重写或添加适当的索引来优化,那么这可能是最好的选择。