Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/redis/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 通过单点高效检索重叠的IP范围记录_Sql_Database_Oracle_Database Design - Fatal编程技术网

Sql 通过单点高效检索重叠的IP范围记录

Sql 通过单点高效检索重叠的IP范围记录,sql,database,oracle,database-design,Sql,Database,Oracle,Database Design,我有一个包含数百万条IP范围记录的表,分别是start_num和end_num,我需要通过单个IP地址查询这些记录,以便返回与该点重叠的所有范围。查询基本上是: SELECT start_num , end_num , other_data_col FROM ip_ranges WHERE :query_ip BETWEEN start_num and end_num; 该表在start_num上有8个范围分区,在start_num、end_num上有一个本地复

我有一个包含数百万条IP范围记录的表,分别是start_num和end_num,我需要通过单个IP地址查询这些记录,以便返回与该点重叠的所有范围。查询基本上是:

SELECT start_num
       , end_num
       , other_data_col 
FROM ip_ranges 
WHERE :query_ip BETWEEN start_num and end_num;
该表在start_num上有8个范围分区,在start_num、end_num上有一个本地复合索引。将其称为UNQ_range_IDX。统计数据已收集在表格和索引上

该查询按预期对UNQ_range_IDX索引进行索引范围扫描,在某些情况下执行得非常好。其性能良好的情况是在IP地址空间的底部,即类似于4.4.10.20的情况,而在高端时性能较差。i、 e.200.2.2.2我确信问题在于,在低端,优化器可以修剪包含适用范围的分区之上的所有分区,因为start_num上的范围分区提供了修剪所需的信息。当在IP频谱的顶端查询时,它不能删减较低的分区,因此会导致读取额外索引分区的I/O。这可以通过跟踪执行时获取的CR_BUFFER_的数量进行验证

事实上,满足查询的范围不会在任何分区中,而是查询ip所在的分区或其正下方或正上方的分区,因为范围大小不会大于一个类,并且每个分区都覆盖许多类。通过在where子句中指定,我可以让Oracle使用该信息,但是有没有办法通过统计数据、直方图或自定义/域索引将此类信息传递给Oracle?在搜索覆盖特定日期的日期范围时,似乎有一种通用的解决方案/方法来解决这类问题

我正在寻找使用Oracle及其功能来解决此问题的解决方案,但也欢迎使用其他类型的解决方案。我已经想到了一些Oracle范围之外的方法,但我希望有一种更好的索引、统计数据收集或分区方法能够起到作用

请求的信息:


为范围分区选择的截止值没有什么特别之处。它们只是一个类地址,其中每个分区的范围数相当于大约1M条记录。

我过去也遇到过类似的问题;我的优势是,我的射程很明显。我有几个IP_范围表,每个表针对特定的上下文,最大的是大约1000万条记录,没有分区

我拥有的每个表都是索引组织的,主键是END_NUM,START_NUM。我在START_NUM,END_NUM上也有一个唯一的索引,但在本例中不使用它

使用随机IP地址1234567890,您的查询大约需要132k个一致的GET

下面的查询根据10.2.0.4上的IP返回4-10个一致的GET

select *
  from ip_ranges outr
 where :ip_addr between outr.num_start and outr.num_end
   and outr.num_end = (select /*+ no_unnest */
                              min(innr.num_end)
                             from ip_ranges innr
                            where innr.num_end >= :ip_addr);
---------------------------------------------------------------------------------------------------
| Id  | Operation                     | Name              | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |                   |     1 |    70 |     6   (0)| 00:00:01 |
|*  1 |  INDEX RANGE SCAN             | IP_RANGES_PK      |     1 |    70 |     3   (0)| 00:00:01 |
|   2 |   SORT AGGREGATE              |                   |     1 |     7 |            |          |
|   3 |    FIRST ROW                  |                   |   471K|  3223K|     3   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN (MIN/MAX)| IP_RANGES_PK      |   471K|  3223K|     3   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - access("OUTR"."NUM_END"= (SELECT /*+ NO_UNNEST */ MIN("INNR"."NUM_END") FROM
              "IP_RANGES" "INNR" WHERE "INNR"."NUM_END">=TO_NUMBER(:IP_ADDR)) AND
              "OUTR"."NUM_START"<=TO_NUMBER(:IP_ADDR))
       filter("OUTR"."NUM_END">=TO_NUMBER(:IP_ADDR))
   4 - access("INNR"."NUM_END">=TO_NUMBER(:IP_ADDR))


Statistics
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
        968  bytes sent via SQL*Net to client
        492  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

最重要的提示是关键;它告诉Oracle运行该子查询一次,而不是每行运行一次,并且它为外部查询中使用的索引提供了一个相等性测试。

我看到的问题是本地分区索引,正如您所说,Oracle似乎没有有效地修剪分区列表。你能试试全局索引吗?对于OLTP查询,本地分区索引不能很好地扩展。在我们的环境中,我们不使用任何本地分区索引。

请说明您的IP范围是否有任何统一或有序的特征?例如,我通常希望IP范围位于二次幂边界上。这里就是这种情况,所以我们可以假设所有范围都有一个隐式的净掩码,它从m个1开始,然后是n个0,其中m+n=32

如果是这样的话,就应该有一种方法来利用这些知识并进入范围。是否可以在计算值上添加一个索引,其掩码位的计数为0-32,或者块大小为1到2^32

仅使用start_num从掩码0到32进行32次搜索要比使用start_num和end_num之间的扫描快


此外,您是否考虑过位算术作为一种可能的方法,仅当范围表示大小为2次幂的均匀定位块时,才再次检查匹配项。

您现有的分区不起作用,因为Oracle正在通过start_num访问表的本地索引分区,它必须检查每一个可能匹配的地方

另一种解决方案是,假设没有范围跨越A类,则按truncstart_num/power256,3(第一个八位组)列出分区。将其拆分为一个通过触发器填充的列,并将其作为筛选列添加到查询中,这可能是值得的

假设分布均匀,那么大约10万行将被分散成大约4万行,这可能会加快阅读速度

我运行了下面讨论的用例,假设没有范围跨越a类网络

create table ip_ranges
 (start_num         number           not null, 
  end_num           number           not null, 
  start_first_octet number           not null,
   ...
  constraint start_lte_end check (start_num <= end_num), 
  constraint check_first_octet check (start_first_octet = trunc(start_num / 16777216) )
)
partition by list ( start_first_octet )
(
partition p_0 values (0),
partition p_1 values (1),
partition p_2 values (2),
...
partition p_255 values (255)
);

-- run data population script, ordered by start_num, end_num

create index ip_ranges_idx01 on ip_ranges (start_num, end_num) local;

begin 
  dbms_stats.gather_table_stats (ownname => user, tabname => 'IP_RANGES', cascade => true);
end;
/
使用上面的基本查询仍然表现不佳,因为它无法进行有效的分区消除:

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 | 25464 |  1840K|   845   (1)| 00:00:05 |       |       |
|   1 |  PARTITION LIST ALL                |                 | 25464 |  1840K|   845   (1)| 00:00:05 |     1 |   256 |
|   2 |   TABLE ACCESS BY LOCAL INDEX ROWID| IP_RANGES       | 25464 |  1840K|   845   (1)| 00:00:05 |     1 |   256 |
|*  3 |    INDEX RANGE SCAN                | IP_RANGES_IDX01 |   825 |       |   833   (1)| 00:00:05 |     1 |   256 |
----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("END_NUM">=TO_NUMBER(:IP_ADDR) AND "START_NUM"<=TO_NUMBER(:IP_ADDR))
       filter("END_NUM">=TO_NUMBER(:IP_ADDR))


Statistics
----------------------------------------------------------
         15  recursive calls
          0  db block gets
     141278  consistent gets
      94469  physical reads
          0  redo size
       1040  bytes sent via SQL*Net to client
        492  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed
但是,如果我们添加允许Oracle专注于单个分区的条件,则会产生巨大的差异:

SQL> select * from ip_ranges
  2   where :ip_addr between start_num and end_num
  3     and start_first_octet = trunc(:ip_addr / power(256,3));

----------------------------------------------------------------------------------------------------------------------
| Id  | Operation                          | Name            | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
----------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |                 |   183 | 13542 |   126   (2)| 00:00:01 |       |       |
|   1 |  PARTITION LIST SINGLE             |                 |   183 | 13542 |   126   (2)| 00:00:01 |   KEY |   KEY |
|   2 |   TABLE ACCESS BY LOCAL INDEX ROWID| IP_RANGES       |   183 | 13542 |   126   (2)| 00:00:01 |   KEY |   KEY |
|*  3 |    INDEX RANGE SCAN                | IP_RANGES_IDX01 |     3 |       |   322   (1)| 00:00:02 |   KEY |   KEY |
----------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("END_NUM">=TO_NUMBER(:IP_ADDR) AND "START_NUM"<=TO_NUMBER(:IP_ADDR))
       filter("END_NUM">=TO_NUMBER(:IP_ADDR))


Statistics
----------------------------------------------------------
         15  recursive calls
          0  db block gets
          7  consistent gets
          0  physical reads
          0  redo size
       1040  bytes sent via SQL*Net to client
        492  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

首先,你的绩效要求是什么

您的分区有一个明确的起始值和结束值,可以通过所有的分区或硬编码来确定,并在下面的函数概念中使用,但您需要修改它以向前/向后移动一个分区

然后,您应该能够编写代码

SELECT * FROM ip_ranges
WHERE :query_ip BETWEEN start_num and end_num
AND start_num between get_part_start(:query_ip) and get_part_end(:query_ip);
应该能够将其锁定到特定分区。然而,如果如您所建议的,您只能将其锁定为八个分区中的三个,那么这仍然是一个大的扫描。我还发布了另一个更激进的答案,这可能更合适

create or replace function get_part_start (i_val in number) 
                              return number deterministic is
  cursor c_1 is 
    select high_value from all_tab_partitions
    where table_name = 'IP_RANGES'
    order by table_owner, table_name;
  type tab_char is table of varchar2(20) index by pls_integer;
  type tab_num is table of number index by pls_integer;
  t_char  tab_char;
  t_num   tab_num;
  v_ind   number;
begin
  open c_1;
  fetch c_1 bulk collect into t_char;
  close c_1;
  --
  for i in 1..t_char.last loop
    IF t_char(i) != 'MAXVALUE' THEN
      t_num(to_number(t_char(i))) := null;
    END IF;
  end loop;
  --
  IF i_val > t_num.last then
    return t_num.last;
  ELSIF i_val < t_num.first then
    return 0;
  END IF;
  v_ind := 0;
  WHILE i_val >= t_num.next(v_ind) loop
    v_ind := t_num.next(v_ind);
    exit when v_ind is null;
  END LOOP;
  return v_ind;
end;
/

我建议你把800万行的桌子改大一点。 谷歌的IP对我来说,目前正在作为

66.102.011.104

您将一条记录存储为66.102.011,其中包含它所属的各个范围。事实上,您至少为每个aaa.bbb.ccc存储一条记录。您可能会得到一个可能是五倍大的表,但是您可以每次只使用几个逻辑IOs而不是分区扫描的成百上千个IOs来锁定相关记录


我怀疑你们所有的数据都会有点过时,因为世界各地的权威机构发布/重新发布了一系列数据,因此,每天/每周重新生成该表的调整应该不是什么大问题。

start_num和end_num的数据类型是什么?start_num和end_num的数据类型是Number?表中和每个分区中有多少行?您可以共享分区脚本吗?我想看看范围和其他细节。另外,为什么要使用局部分区索引?为什么不使用全局分区索引呢?表中的行数不到8M,每个分区大约有1M行。感谢您的回复,但这与我的案例中所写的不一样。在原始查询中返回4行的查询在您的方案中不返回任何行。这是因为对于重叠的范围,您的内部查询可能会返回一个与外部查询中的范围不关联的end_num。例如,一个IP地址4.4.4.4可能在一个巨大的a类范围4.0.0.0-4.255.255.255中,但前面有一个较小的范围4.4.5.0-255,这将由内部查询返回。您的查询将永远不会返回多行,除非结束符都是相同的,否则我会将其放在那里,以防其他人遇到我遇到的问题,这确实解决了在非重叠范围存在匹配行时快速查找匹配行的优化问题。我刚才的一个想法是,空间中的一些选项,特别是使用SDO_点/SDO_线、R树索引和覆盖操作符可能会有所帮助,但是Spatial要花钱,而且我没有许可证。性能行为与本地分区索引相同。你是说全局分区索引吗?全局分区索引和本地分区索引提供相同类型的性能。无论分区是本地分区还是全局分区,问题仍然是优化器无法基于以下内容修剪分区:query_ip在start_num和end_num之间。如果您有全局分区索引,并且CBO使用索引,则应自动进行修剪。是否可以提供具有较低和较高IP范围值的自动跟踪输出?你现在的版本是什么?你能试试这个查询吗?从ip范围中选择开始数、结束数、其他数据列,其中:在开始数和结束数之间查询ip,开始数>=:查询ipCan我们不可以在第一个八位字节上创建全局分区函数索引吗?所以在开始数上创建一个索引,按truncstart\u num/power256,3结束分区,并将查询更改为include和truncstart\u num/power256,3=trunc:ip\u addr/power256,3?这可能行得通——我还没有测试过——但它似乎很聪明,我个人的规则之一就是尽可能避免全局分区索引;它们排除了太多高效的分区表操作。在我看来,这似乎是一种双重的间接聪明,这种聪明会驱使下一个可怜的混蛋发疯,试图理解那个索诺娃^&!%@我在想。该死的600字限制!OP说为范围分区选择的截止值没有什么特别之处。它们只是一个类地址,其中每个分区的范围数相当于大约1M条记录。如果范围不跨越类A记录而不是强制索引,那么为什么不修复分区方案呢?我不认为A类的划分除了使分区更小之外,还有很大的作用。如果优化器不扫描上层IP上的所有分区,那就更好了。如果我查询的IP为205.x.x.x,那么所有的分区,无论是在start_num上,还是在start_num为205.x.x.x的分区下面的A-class上,都可能有一个end_num超出我的查询IP,因此需要
被退回。我可以在start_num上添加一个额外的过滤器,以限制它可以向后看多远,但是如果在某个地方插入了一个异常大的范围,则可能找不到。如果Oracle根据统计数据或分区1-5没有足够大的结束时间来拥有与查询匹配的记录,那就太好了,但我不确定是否有可能做到这一点。空间可能会起作用,但我认为我们也没有这个选择。这基本上也是我想做的事情。我不能真正改变表格的格式,因为有太多的东西链接到它,但我考虑使用类似的方法来创建一个表格,它本质上是作为现有表格的索引。基本上,创建一个非唯一索引,其中包含c类和现有表中与该c类重叠的所有记录。然后更改SQL以查询此表中与查询ip c类重叠的所有范围,然后从该小子集中筛选出不与该特定ip重叠的任何范围。我唯一的缺点是,在从现有表中添加和删除范围时,我必须管理此表。如果没有其他解决方案可以让Oracle完成所有工作,我会尝试这样做。谢谢+1.
create or replace function get_part_start (i_val in number) 
                              return number deterministic is
  cursor c_1 is 
    select high_value from all_tab_partitions
    where table_name = 'IP_RANGES'
    order by table_owner, table_name;
  type tab_char is table of varchar2(20) index by pls_integer;
  type tab_num is table of number index by pls_integer;
  t_char  tab_char;
  t_num   tab_num;
  v_ind   number;
begin
  open c_1;
  fetch c_1 bulk collect into t_char;
  close c_1;
  --
  for i in 1..t_char.last loop
    IF t_char(i) != 'MAXVALUE' THEN
      t_num(to_number(t_char(i))) := null;
    END IF;
  end loop;
  --
  IF i_val > t_num.last then
    return t_num.last;
  ELSIF i_val < t_num.first then
    return 0;
  END IF;
  v_ind := 0;
  WHILE i_val >= t_num.next(v_ind) loop
    v_ind := t_num.next(v_ind);
    exit when v_ind is null;
  END LOOP;
  return v_ind;
end;
/