Era*_*ran 11 mysql sql performance
我正在使用MySQL数据库,并具有下表:
CREATE TABLE SomeTable (
PrimaryKeyCol BIGINT(20) NOT NULL,
A BIGINT(20) NOT NULL,
FirstX INT(11) NOT NULL,
LastX INT(11) NOT NULL,
P INT(11) NOT NULL,
Y INT(11) NOT NULL,
Z INT(11) NOT NULL,
B BIGINT(20) DEFAULT NULL,
PRIMARY KEY (PrimaryKeyCol),
UNIQUE KEY FirstLastXPriority_Index (FirstX,LastX,P)
) ENGINE=InnoDB;
Run Code Online (Sandbox Code Playgroud)
该表包含430万行,初始化后永远不会更改.
此表的重要列FirstX,LastX,Y,Z和P.
正如你所看到的,我对行的唯一索引FirstX,LastX和P.
列FirstX并LastX定义一系列整数.
我需要在此表上运行的查询为给定的X提取所有具有FirstX <= X <= LastX的行(即,其范围包含输入数X的所有行).
例如,如果表包含行(我只包含相关列):
FirstX LastX P Y Z
------ ------ - --- ---
100000 500000 1 111 222
150000 220000 2 333 444
180000 190000 3 555 666
550000 660000 4 777 888
700000 900000 5 999 111
750000 850000 6 222 333
Run Code Online (Sandbox Code Playgroud)
我需要,例如,包含值185000的3行,应返回第一行.
我试过的应该使用索引的查询是:
SELECT P, Y, Z FROM SomeTable WHERE FirstX <= ? AND LastX >= ? LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
即使没有LIMIT,此查询也应该50为任何给定的X 返回少量记录(小于).
此查询由Java应用程序执行,120000值为X.令我惊讶的是,它耗时超过10小时(!),每次查询的平均时间为0.3秒.
这是不可接受的,甚至不可接受.它应该快得多.
我检查了一个耗时0.563秒的查询,以确保使用索引.我尝试的查询(与上面的查询相同而不是特定的整数值?)返回了2行.
我曾经EXPLAIN发现发生了什么:
id 1
select_type SIMPLE
table SomeTable
type range
possible_keys FirstLastXPriority_Index
key FirstLastXPriority_Index
key_len 4
ref NULL
rows 2104820
Extra Using index condition
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,执行涉及2104820行(表的近50%的行),即使只有2行满足条件,因此检查索引的一半以便仅返回2行.
查询或索引有问题吗?您能否建议对查询或索引进行改进?
编辑:
一些答案表明我为X的多个值批量运行查询.我不能这样做,因为我实时运行此查询,因为输入到达我的应用程序.每次输入X到达时,我必须执行X的查询并对查询的输出执行一些处理.
我找到了一个依赖于表中数据属性的解决方案.我宁愿有一个更通用的解决方案,不依赖于当前数据,但暂时是我所拥有的最好的解决方案.
原始查询的问题:
SELECT P, Y, Z FROM SomeTable WHERE FirstX <= ? AND LastX >= ? LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
是,执行可能需要扫描的条目的大百分比的FirstX,LastX,P索引当第一条件FirstX <= ?是由所述行的大百分比满足.
我所做的减少执行时间的观察LastX-FirstX是相对较小的.
我运行了查询:
SELECT MAX(LastX-FirstX) FROM SomeTable;
Run Code Online (Sandbox Code Playgroud)
并得到了4200000.
这意味着FirstX >= LastX – 4200000对于表中的所有行.
所以为了满足LastX >= ?,我们也必须满足FirstX >= ? – 4200000.
所以我们可以为查询添加一个条件,如下所示:
SELECT P, Y, Z FROM SomeTable WHERE FirstX <= ? AND FirstX >= ? - 4200000 AND LastX >= ? LIMIT 10;
Run Code Online (Sandbox Code Playgroud)
在我在问题中测试的示例中,处理的索引条目的数量从减少到2104820,18并且运行时间从0.563秒减少到0.0003秒.
我用相同的120000值测试了新查询X.输出与旧查询相同.时间从10多个小时下降到5.5分钟,快了100多倍.
WHERE col1 < ... AND ... < col2 几乎不可能优化.
任何有用的查询都将涉及col1或col2上的"范围".两个范围(在两个不同的列上)不能单独使用INDEX.
因此,您尝试的任何索引都有可能会检查很多表:
INDEX(col1, ...)将从开始扫描到col1命中....同样地用于col2扫描直到结束.
要添加到您的困境中,范围是重叠的.所以,你不能拉一个快速的,并添加ORDER BY ... LIMIT 1快速停止.如果你说LIMIT 10,但只有9,它将不会停止,直到表的开始/结束.
你可以做的一件简单的事情(但它不会加快速度)是交换PRIMARY KEY和UNIQUE.这可能会有所帮助,因为InnoDB会将PK与数据"聚集"在一起.
如果范围没有重叠,我会指向http://mysql.rjweb.org/doc.php/ipranges.
那么,可以做些什么?范围"均匀"和"小"是多少?如果它们合理地"好",那么下面将采用一些代码,但应该快得多.(在你的例子中,100000 500000非常难看,你会在一分钟内看到.)
将桶定义为地板(数量/ 100).然后构建一个关联存储桶和范围的表.样品:
FirstX LastX Bucket
123411 123488 1234
222222 222444 2222
222222 222444 2223
222222 222444 2224
222411 222477 2224
Run Code Online (Sandbox Code Playgroud)
注意一些范围如何"属于"多个桶.
然后,首先搜索查询中的存储区,然后搜索详细信息.寻找X = 222433将找到两行,其中bucket = 2224,然后确定两者都没问题.但是对于X = 222466,两行有桶,但只有一行与firstX 和 lastX 匹配.
WHERE bucket = FLOOR(X/100)
AND firstX <= X
AND X <= lastX
Run Code Online (Sandbox Code Playgroud)
同
INDEX(bucket, firstX)
Run Code Online (Sandbox Code Playgroud)
但是...... 100000 500000有4001行,因为这个范围在很多"桶"中.
B计划(解决范围广泛)
将范围分为宽和窄.通过简单的表扫描进行宽范围,通过我的桶方法进行窄范围. UNION ALL结果在一起.希望"宽"表比"窄"表小得多.
小智 1
您需要在 LastX 上添加另一个索引。
唯一索引 FirstLastXPriority_Index (FirstX,LastX,P) 表示这些值的串联,因此对于 'AND LastX >= ?' 将毫无用处 WHERE 子句的一部分。