2

我正在尝试根据 IP 地址查询一个相当大的 IP/城市(3,000,000+ 行)表。我的源 IP 地址是点表示法,例如 127.0.0.1,并且表中有两个字段存储为整数,例如 2130706433。所以我需要像这样在 where 子句中将点表示法转换为整数;

select get_ip_integer('74.253.103.98') ,icb.*,icl.* 
from ip_city_block icb, ip_city_location icl 
where get_ip_integer('74.253.103.98') between  icb.startipnum and  icb.endipnum  and
      icl.locid = icb.locid;

在相当快的数据库上,此查询需要 4 ~(4.33) 秒。以下查询需要 0.062 秒;

select 1258121058,icb.*,icl.* 
from ip_city_block icb, ip_city_location icl 
where icb.startipnum <= 1258121058 and  icb.endipnum >= 1258121058 and
      icl.locid = icb.locid;

唯一的区别是我将函数 get_ip_integer 替换为函数返回的值。如果我所做的只是一次查找,我将执行第二个查询并完成它,但我不是。

我实际上想加入另一个包含许多点格式 IP 地址的表,而当我这样做时,它需要很长时间。为了好玩,我也试过;

select ip_integer ,icb.*,icl.* 
from (select get_ip_integer('74.253.103.98') ip_integer from dual),ip_city_block icb, ip_city_location icl 
where icb.startipnum <= ip_integer and  icb.endipnum >= ip_integer and
      icl.locid = icb.locid;

这也花了大约 4.33 秒。

所以问题是如何强制 get_ip_integer 函数只执行一次并使用结果进行比较?

我将我的函数更新为 Deterministic,它似乎对原始查询有所帮助,但更复杂的查询仍然无法使用,性能明智。这里是;

SELECT COUNTRY, REGION,CITY, WEBLOG_USERID, WEBLOG_IP, WEBLOG_COUNT 
FROM (
  select WEBLOG_USERID,WEBLOG_IP,get_ip_integer(WEBLOG_IP) ip_integer,count(*) WEBLOG_COUNT 
  from weblog 
  where weblog_date > '20130217' 
  group by WEBLOG_USERID,weblog_ip
),ip_city_block icb, ip_city_location icl 
where ip_integer between icb.startipnum and icb.endipnum and icl.locid = icb.locid
ORDER BY 1,2,3;

对此有什么想法吗?

经过我自己的一点思考,我想出了这个,虽然不是快得令人眼花缭乱,但它是可以接受的;

SELECT COUNTRY, REGION,CITY, WEBLOG_USERID, WEBLOG_IP, WEBLOG_COUNT 
FROM (
  select WEBLOG_USERID,WEBLOG_IP, count(*) WEBLOG_COUNT 
  from weblog 
  where weblog_date > '20130000' 
  group by WEBLOG_USERID,weblog_ip
),ip_city_block icb, ip_city_location icl 
where get_ip_integer(WEBLOG_IP) between icb.startipnum and icb.endipnum and icl.locid = icb.locid
ORDER BY 1,2,3;
4

2 回答 2

2

你为什么要为此使用 PL/SQL?根据您所说的,您正在做一些数学运算,为什么不直接在 SQL 中做呢?这也可以通过 INSTR 和 SUBSTR 的组合来实现,但使用 REGEXP_SUBSTR 看起来更漂亮。

select to_number(regexp_substr(ip, '[^.]+', 1, 1)) * power(2,24)
        + to_number(regexp_substr(ip, '[^.]+', 1, 2)) * power(2,16)
        + to_number(regexp_substr(ip, '[^.]+', 1, 3)) * power(2,8)
        + to_number(regexp_substr(ip, '[^.]+', 1, 4))
     , icb.*
     , icl.* 
  from ip_city_block icb
  join ip_city_location icl
    on icl.locid = icb.locid  
 where to_number(regexp_substr(ip, '[^.]+', 1, 1)) * power(2,24)
        + to_number(regexp_substr(ip, '[^.]+', 1, 2)) * power(2,16)
        + to_number(regexp_substr(ip, '[^.]+', 1, 3)) * power(2,8)
        + to_number(regexp_substr(ip, '[^.]+', 1, 4))
       between icb.startipnum and icb.endipnum

REGEXP_SUBSTR 输出的 SQL Fiddle 演示

如果你必须在 PL/SQL 中这样做,你应该做两件事:

  1. 看看你是否可以将你的函数声明为确定性的。
  2. 尝试并利用子查询缓存

看起来好像您已经在做 2,但您可以尝试通过使用 WITH 子句来扩展它:

with the_ip as ( select get_ip_integer('74.253.103.98') as ip from dual )
select the_ip.ip
     , icb.*
     , icl.* 
  from ip_city_block icb
  join ip_city_location icl
    on icl.locid = icb.locid
  join the_ip
    on the_ip.ip between icb.startipnum and icb.endipnum
于 2013-02-19T18:22:35.437 回答
2

您在双轨上走在了正确的轨道上,但是对于子查询缓存,您可以在选择中执行此操作。

select (select get_ip_integer('74.253.103.98') from dual) ip,
       icb.*,icl.* 
from ip_city_block icb, ip_city_location icl 
where get_ip_integer('74.253.103.98') between  icb.startipnum and  icb.endipnum  and
      icl.locid = icb.locid;

你也应该用result_cache.

有关详细信息,请参见此处:http ://www.oracle.com/technetwork/issue-archive/2011/11-sep/o51asktom-453438.html

于 2013-02-19T18:27:55.007 回答