提问人:Pacemaker 753 提问时间:11/17/2023 最后编辑:Pacemaker 753 更新时间:11/19/2023 访问量:89
更好的方法或数据库来存储 5000 万个范围数据和快速搜索操作
Better approach or database to store 50 million of range data and fast search operation
问:
问题陈述:我们将收到一个数字(11 位数字)的请求,并且必须在数据库中有效地查找并根据其适合的范围返回一行(上次更新)。 此外,我已经使用过 mysql 并使用适当的索引进行了负载测试,但是这需要更多时间。
当前数据库结构:
使用 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
Table 2 name: 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
要优化的查询:我需要重新设计或优化查询,以便高效、快速地执行。
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;
解释查询:
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
解释查询的分析:
'-> Limit: 1 row(s) (cost=65515 rows=0.104) (actual time=529..529 rows=1 loops=1)\n -> Nested loop left join (cost=65515 rows=0.104) (actual time=529..529 rows=1 loops=1)\n -> Filter: ((a.is_active = 1) and (535387481 between a.low_range and a.high_range)) (cost=0.21 rows=0.104) (actual time=529..529 rows=1 loops=1)\n -> Index scan on a using PRIMARY (reverse) (cost=0.21 rows=2) (actual time=1.25..490 rows=531614 loops=1)\n -> Filter: (a.code = b.id) (cost=0.961 rows=1) (actual time=0.0347..0.0347 rows=1 loops=1)\n -> Single-row index lookup on b using PRIMARY (id=a.code) (cost=0.961 rows=1) (actual time=0.0331..0.0331 rows=1 loops=1)\n'
要求:12345678912
表中可能的行:
low_range: 12345678901 high_range: 12345678913
low_range: 12345678910 high_range: 12345678912
low_range: 12345678902 high_range: 12345678920
问题:
哪个数据库可用于存储数百万个范围数据,并具有高效的查找功能。如果可能的话,任何更好的 mysql 方法。
我已经使用过mysql并使用适当的索引进行了负载测试,但是这需要更多时间。 期望查找应该是快速高效的~500毫秒。
答:
PostgreSQL 内置了范围类型,这些类型提供了 GiST 和 SP-GiST 索引支持的 @>
包含运算符。问题是,索引查找的性能是由样本的分布驱动的。如果我有目的地将所有填充样本移出目标范围,我可以在 0.06 毫秒内从 50M 行中挑选出目标:demo1(这些演示是我所做的测试的缩小示例)
查询计划 |
---|
仅索引 在 public.test 上使用test_r_idx扫描(成本 = 0.42..26414.98 行 = 457289 宽度 = 22)(实际时间 = 0.043..0.047 行 = 3 循环 = 1) |
输出:r |
索引 Cond:(test.r @> '12345678912'::bigint) |
堆提取:0 |
规划时间:0.234 ms |
执行时间:0.060 ms |
如果我完全随机生成它们,情况是一样的,但将范围宽度缩小到 100(示例中的范围宽度是 12、2、18)——在 50M 的范围内,如此高的数字不太可能重叠,选择性高,索引扫描速度快。如果我将范围宽度限制提高到 100k,在目标范围内产生更多命中,它仍然只需要 0.16 毫秒,如果我进一步达到 10M 并有更多的匹配,则仍然需要 4.6 毫秒。演示2
查询计划 |
---|
仅索引 在 public.test 上使用 test_r_idx 进行扫描(成本 = 0.42..648.35 行 = 11196 宽度 = 22)(实际时间 = 0.102..4.182 行 = 12722 循环 = 1) |
输出:r |
索引 Cond:(test.r @> '12345678912'::bigint) |
堆提取:0 |
规划时间:0.056 ms |
执行时间:4.584 ms |
如果我生成 50M 完全随机的范围,它们中的大多数都会跨得很宽,不太可能不重叠并抓住目标,这使得它全部跳到 7 秒,因为它经历了近一半的堆,因为索引在这样的情况下并没有真正的帮助
select setseed(0.1);
create table test (r int8range);
insert into test
select int8range(least(a,b),greatest(a,b),'[]') from (
select (random()*2e10)::int8 a,(random()*2e10)::int8 b
from generate_series(1,(random()*15e6)::int))_;
insert into test values (int8range(12345678901,12345678913,'[]'));
insert into test
select int8range(least(a,b),greatest(a,b),'[]') from (
select (random()*2e10)::int8 a,(random()*2e10)::int8 b
from generate_series(1,(random()*15e6)::int))_;
insert into test values (int8range(12345678910,12345678912,'[]'));
insert into test
select int8range(least(a,b),greatest(a,b),'[]') from (
select (random()*2e10)::int8 a,(random()*2e10)::int8 b
from generate_series(1,(random()*15e6)::int))_;
insert into test values (int8range(12345678902 ,12345678920,'[]'));
insert into test
select int8range(least(a,b),greatest(a,b),'[]') from (
select (random()*2e10)::int8 a,(random()*2e10)::int8 b
from generate_series(1,(select 5e7-count(*) from test)))_;
create index on test using gist(r);
vacuum analyze test;
explain analyze verbose
select * from test where r@>12345678912;
查询计划 |
---|
在 public.test 上进行顺序扫描(成本=0.00..943473.30 rows=23519755 width=22)(实际时间=18.990..6629.042 rows=23618706 loops=1) |
输出:r |
过滤器:(test.r @> '12345678912'::bigint) |
按筛选器删除的行数:26381294 |
计划时间:0.069 ms |
JIT: |
功能:2 |
选项:内联 true、优化 true、表达式 true、变形 true |
定时:生成 0.355 ms,内联 3.575 ms,优化 11.550 ms,发射 3.765 ms,总计 19.244 ms |
执行时间:7608.159 ms |
评论
is_active
WHERE is_active = 1
)
boolean
smallint
tinyint
https://mysql.rjweb.org/doc.php/ipranges 中的代码显示了如何改进 IP 地址的查找。它使MySQL的性能与Postgres一样快(基本上是为简单查找获取1行)。你有;这应该同样有效。DECIMAL(11,0)
它确实涉及将结构更改为每行只有一个数字。如果存在间隙,则通过具有 [大概] is_active=false 的额外虚拟行来处理它们。
评论
range
multirange
ORDER BY...LIMIT 1
INDEX
评论