如何强制 where 子句中的函数在 oracle 中执行一次?
Posted
技术标签:
【中文标题】如何强制 where 子句中的函数在 oracle 中执行一次?【英文标题】:How can you force an function in a where clause to execute once in oracle? 【发布时间】:2013-02-19 18:17:43 【问题描述】:我正在尝试根据 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;
【问题讨论】:
高度相关/可能的欺骗:***.com/q/7270467/458741,基本上是子查询缓存,您似乎已经尝试过...... 有没有这个功能的方案是什么?默认情况下,优化器假定使用自定义函数的条件将返回 1/20 的行。而优化器可能能够通过使用直方图或仅使用列的独特性来使用硬编码值做出更好的估计。比较解释计划,特别是查看基数和访问路径。我会认为一个计划有一个嵌套循环,另一个有一个哈希连接。 【参考方案1】:您为什么要为此使用 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
SQL Fiddle demonstration of REGEXP_SUBSTR output
如果你有在 PL/SQL 中这样做,你应该做两件事:
-
看看你是否可以将你的函数声明为deterministic。
尝试利用sub-query caching。
看起来好像你已经在做 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
【讨论】:
不幸的是,数据存储为整数计算; a*2^24 + b*2^16 + c*2^8+d,其中 a.b.c.d 是 IP 地址。使用您的方法,字符串 11111111 可以解释为 1.111.1.111 或 11.11.11.11 或... Ben,我接受了您的查询,并按原样运行,它在 ~4.275 秒内执行。 @Paul,我已经用一种在 SQL 而不是 PL/SQL 中执行此操作的方法更新了我的答案。在 4.275s 运行时,您是否将函数声明为确定性?可以吗? 我已将函数更改为确定性,它提高了性能以等于值。然而,更复杂的查询仍然像蛞蝓一样运行。我将使用更复杂的查询更新我的原始问题。 @PaulStearns,除非它确实解决了您的问题,否则无需接受此答案。做到了?如果您的问题没有解决,请等待其他可能提供帮助的人。我注意到您在技术上并没有在更复杂的查询中使用子查询缓存并且有一个 ORDER BY。这是必要的吗,因为没有它你可以提高速度。【参考方案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
【讨论】:
DazzaL,result_cache 位将执行时间缩短到 ~1.51 秒。好多了,但在我使用该值时不会下降到 0.062 秒。以上是关于如何强制 where 子句中的函数在 oracle 中执行一次?的主要内容,如果未能解决你的问题,请参考以下文章