存储和高效查找范围数据

Storage and efficient lookup for range data

提问人:Pacemaker 753 提问时间:11/7/2023 最后编辑:ShadowPacemaker 753 更新时间:11/8/2023 访问量:58

问:

问题陈述:我们将收到一个数字(11 位数字)的请求,并且必须在数据库中有效地查找并根据其适合的范围返回一行(上次更新)。

当前数据库结构:

使用 MySQL

目前,我们有一个表,它有 2 列,即 low_range 和 high_range,用于存储数据范围,还有另外 2 列用于存储相应的数据,即 is_active(值可以是 0 和 1)和代码(int 值,它是另一个表的 id,即 code_mapping)。

表1名称:range_mapping

数据库架构:

create table range_mapping (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`low_range` decimal(11,0) NOT NULL,
`high_range` decimal(11,0) NOT NULL,
`is_active` tinyint(1) NOT NULL DEFAULT 1,
`code` int(8) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_comp_is_active_low_high_range` (`is_active`, `low_range`, `high_range`)
) ENGINE=InnoDB AUTO_INCREMENT=26891234 DEFAULT CHARSET=utf8

表2名称:code_mapping

数据库架构:

create table code_mapping (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `nameIdx` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=4410 DEFAULT CHARSET=utf8

要优化的查询: 我需要重新设计或优化查询,以便高效、快速地执行。

要求:12345678912

表中可能的行:

low_range: 12345678901 high_range: 12345678913
low_range: 12345678910 high_range: 12345678912
low_range: 12345678902 high_range: 12345678920
select a.low_range, a.high_range, b.name from range_mapping AS a 
LEFT JOIN code_mapping AS b ON a.code = b.id 
WHERE a.is_active = 1 and 12345678912 BETWEEN a.low_range AND a.high_range 
ORDER BY a.id DESC 
limit 1;

问题: 当以 20 个请求/秒执行上述查询时,最多需要 ~20 秒。我需要优化查询或数据库,使其在 500 毫秒内执行。

我已经添加了复合索引,它以某种方式优化了查询,并且还使用了强制索引作为查询的一部分。它仍然需要 2 秒多。

解释查询:

explain select a.low_range, a.high_range, b.name from range_mapping AS a force index(idx_comp_is_active_low_high_range) LEFT JOIN code_mapping AS b ON a.code = b.id WHERE a.is_active = 1 and 12345678912 BETWEEN a.low_range AND a.high_range ORDER BY a.id DESC limit 1; 

输出:

id: 1
select_type: SIMPLE
table: a
type: range
possible_keys: idx_comp_is_active_low_high_range
key: idx_comp_is_active_low_high_range
key_len: 11
ref: NULL
rows: 227190
Extra: Using index condition; Using filesort

***************************
id: 1
select_type: SIMPLE
table: b
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: testbackup.a.code
rows: 1
Extra: Using where

期望:如何改进数据库架构或优化查询以在毫秒内获取数据。

MySQL 索引 -优化 范围-查询

评论

0赞 Shadow 11/7/2023
您可以尝试在索引末尾添加 range_mapping.id 吗?这可以摆脱解释中的文件排序idx_comp_is_active_low_high_range
0赞 Pacemaker 753 11/8/2023
这已经尝试过了。但是,当并发命中完成 30 db 查询/秒时,它并没有提高性能。
0赞 Rick James 11/9/2023
请参阅 [IP 范围}(mysql.rjweb.org/doc.php/ipranges),了解可能适合您的非常有效的方法。(您的低/high_range 的工作方式类似于 IP 地址。
0赞 Pacemaker 753 11/13/2023
@RickJames 让我检查并确认

答:

1赞 O. Jones 11/7/2023 #1

您正在寻找最大值 (from )的匹配行,该行与您的查询筛选器匹配。这意味着您可以像这样重写查询。a.idORDER BY a.id DESC LIMIT 1)

让我们从一个子查询开始。

SELECT MAX(id) id 
  FROM range_mapping 
 WHERE is_active = 1
   and low_range <= 12345678912
   AND high_range >= 12345678912

这个子查询很辛苦。它必须在你的表中寻找很多东西,所以简化它是件好事。

用这个复合指数可以加速它。

CREATE INDEX idx_comp_is_active_low_id_high_range ON range_mapping
    (is_active, low_range, id DESC, high_range)

为了满足子查询,MySQL将根据前两列(和)随机访问第一个符合条件的行的索引。然后,它将扫描索引,查找与条件匹配的第一个索引行,并从该索引条目返回值。它已经是最大的一个了。is_activelow_rangehigh_rangeid

请注意,我定义的索引与您拥有的索引几乎相同,只是添加了 作为倒数第二列。id

接下来,我们需要使用该值从第一个表和第二个表中检索详细信息。事情是这样的。range_mapping.idname

SELECT a.low_range, a.high_range, b.name
  FROM (
        SELECT MAX(id) id 
          FROM range_mapping 
         WHERE is_active = 1
           AND low_range <= 12345678912
           AND high_range >= 12345678912
       ) AS found
  JOIN range_mapping AS a ON found.id = a.id
  LEFT JOIN code_mapping AS b ON a.code = b.id

这应该更快。它不必对任何内容进行排序,也不必加入多行。

你可以用同样的方式用 BETWEEN 来写它。

SELECT a.low_range, a.high_range, b.name
  FROM (
        SELECT MAX(id) id 
          FROM range_mapping 
         WHERE is_active = 1
           AND 12345678912 BETWEEN low_range AND high_range
       ) AS found
  JOIN range_mapping AS a ON found.id = a.id
  LEFT JOIN code_mapping AS b ON a.code = b.id

评论

0赞 Pacemaker 753 11/8/2023
从range_mapping中选择 MAX(id) id,其中 is_active = 1 且 low_range <= 12345678912 和 high_range >= 12345678912 在上面的查询中,您使用了比较运算符和相同的请求号,即 12345678912 ,但根据我问题中提到的查询,我不知道最低和最高范围。这就是使用 between 的原因。例如:可以有一行这样的行:low_range:12345678901 high_range:12345678913和请求:12345678912属于上述范围。但是,您的查询不会提取此行。
0赞 O. Jones 11/8/2023
12345678912 BETWEEN low_range AND high_range相当于我写的,物有所值。无论如何,我的答案价值并不重要。请参阅我的编辑。
0赞 Pacemaker 753 11/13/2023
非常感谢琼斯@O。我执行了上面的查询,它在 50-100 毫秒内执行。但是,当我进行大量此类查询时,平均需要 686 毫秒、最小值 (159 毫秒) 和最大值 (686 毫秒),而我的查询平均需要 87 毫秒、最小值 (22 毫秒) 和最大值 (208 毫秒)。负载测试在具有 20 个用户的 4 核计算机上完成,持续时间长达 40 秒。因此,我不确定哪个会表现得更好,因为数据和负载最终会增加。
1赞 O. Jones 11/13/2023
查询优化既是一门艺术,也是一门科学。很难预测查询执行计划将如何随着表大小的扩展而变化。这就是为什么数据库运维人员在应用程序的整个生命周期内定期重新审视查询性能的原因。