搜索满足Column1 <= X <= Column2的行的SQL查询非常慢

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,ZP.

正如你所看到的,我对行的唯一索引FirstX,LastXP.

FirstXLastX定义一系列整数.

我需要在此表上运行的查询为给定的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)

我需要,例如,包含值1850003行,应返回第一行.

我试过的应该使用索引的查询是:

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的查询并对查询的输出执行一些处理.

Era*_*ran 9

我找到了一个依赖于表中数据属性的解决方案.我宁愿有一个更通用的解决方案,不依赖于当前数据,但暂时是我所拥有的最好的解决方案.

原始查询的问题:

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多倍.


Ric*_*mes 5

WHERE col1 < ... AND ... < col2 几乎不可能优化.

任何有用的查询都将涉及col1或col2上的"范围".两个范围(在两个不同的列上)不能单独使用INDEX.

因此,您尝试的任何索引都有可能会检查很多表: INDEX(col1, ...)将从开始扫描到col1命中....同样地用于col2扫描直到结束.

要添加到您的困境中,范围是重叠的.所以,你不能拉一个快速的,并添加ORDER BY ... LIMIT 1快速停止.如果你说LIMIT 10,但只有9,它将不会停止,直到表的开始/结束.

你可以做的一件简单的事情(但它不会加快速度)是交换PRIMARY KEYUNIQUE.这可能会有所帮助,因为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 子句的一部分。