Home > oracle > SQL优化案例 (提高百万级别速度)

SQL优化案例 (提高百万级别速度)

January 2nd, 2009

案例描述如下:

表ip_postal_code_mapping中有两个列start_ip_number, end_ip_number分别表述IP地址段的起始地址和结束地址,已经将IP地址通过函数转化为number类型

比方说 192.168.1.1 = 192168001001

现在需要查看这些地址段相互之间有没有交叠的,例如192.168.1.1~192.168.1.20 和192.168.1.10~192.168.1.30即为交叠的地址段

比较自然的SQL语句写法如下:

select a.start_ip_number,a.end_ip_number,b.start_ip_number,b.end_ip_number from
ip_postal_code_mapping a,ip_postal_code_mapping b
where a.start_ip_number between b.start_ip_number and b.end_ip_number;

SQL> explain plan for select a.start_ip_number,a.end_ip_number,b.start_ip_number,b.end_ip_number from
2 ip_postal_code_mapping a,ip_postal_code_mapping b
3 where a.start_ip_number between b.start_ip_number and b.end_ip_number;

Explained.

SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY());

PLAN_TABLE_OUTPUT
———————————————–
Plan hash value: 2080022078

—————————————————————————————————————
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
—————————————————————————————————————
| 0 | SELECT STATEMENT | | 1 | 52 | 4 (0)| 00:00:01 | | |
| 1 | NESTED LOOPS | | 1 | 52 | 4 (0)| 00:00:01 | | |
| 2 | PARTITION RANGE ALL| | 1 | 26 | 2 (0)| 00:00:01 | 1 | 6 |
| 3 | TABLE ACCESS FULL | IP_POSTAL_CODE_MAPPING | 1 | 26 | 2 (0)| 00:00:01 | 1 | 6 |
| 4 | PARTITION RANGE ALL| | 1 | 26 | 2 (0)| 00:00:01 | 1 | 6 |
|* 5 | TABLE ACCESS FULL | IP_POSTAL_CODE_MAPPING | 1 | 26 | 2 (0)| 00:00:01 | 1 | 6 |

PLAN_TABLE_OUTPUT
—————————————————————————————-

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

5 – filter(“A”.”START_IP_NUMBER”>=”B”.”START_IP_NUMBER” AND
“A”.”START_IP_NUMBER”<="B"."END_IP_NUMBER")

18 rows selected.

这个表有两百多万条记录,所以这个Nest Loop的plan非常的糟糕,而且也没有办法使用hash join,因为没有等式比较。事实证明这个SQL跑了七个小时以上还是没有跑出来。

优化的思路是这样的

1. 将表中的记录按照IP地址段的起始地址进行排序,并添加ID列标志排序的位置存入临时表中

通过排序,使得START_IP_NUMBER按照ID的递增而递增

ID START_IP_NUMBER END_IP_NUMBER
1 S1 E1
2 S2 E2
3 S3 E3
。。。

create table temp as
select rownum id, start_ip_number ,end_ip_number
from STAGING_IPMAP_USER.ip_postal_code_mapping
where version=20081217
order by start_ip_number,end_ip_number;

SQL> select count(*) from temp;

COUNT(*)
———-
2190818

2. 确认单个IP地址段的正确性 (START_IP_NUMBER必须小于等于END_IP_NUMBER)

ID START_IP_NUMBER END_IP_NUMBER
1 S1 <= E1
2 S2 <= E2
3 S3 <= E3
。。。

SQL> select * from temp where START_IP_NUMBER>END_IP_NUMBER;

no rows selected

3. 将任意记录的IP地址的结束地址和下一条记录的起始地址进行比较

如果所有记录都满足条件,则 Sn > En-1

结合Step2得到 S1<=E1

这样就说明IP地址段是没有重复的,任何违反这一条件的都是会出现有交叠的IP地址段

SQL> select a.*,b.* from temp a,temp b
2 where a.id=b.id+1 and a.START_IP_NUMBER<=b.END_IP_NUMBER;

SQL> SELECT * FROM table(DBMS_XPLAN.DISPLAY());

PLAN_TABLE_OUTPUT
——————————————————

———————————————————————————–
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
———————————————————————————–
| 0 | SELECT STATEMENT | | 109K| 4064K| | 11926 (28)| 00:00:23 |
|* 1 | HASH JOIN | | 109K| 4064K| 64M| 11926 (28)| 00:00:23 |
| 2 | TABLE ACCESS FULL| TEMP | 2190K| 39M| | 1611 (56)| 00:00:04 |
| 3 | TABLE ACCESS FULL| TEMP | 2190K| 39M| | 1611 (56)| 00:00:04 |
———————————————————————————–

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

1 – access(“A”.”ID”=”B”.”ID”+1)
filter(“A”.”START_IP_NUMBER”<="B"."END_IP_NUMBER")

16 rows selected.

这样可以通过ID列进行hash join,大大提高了速度,执行时间只需要16秒。从原来200万次FTS到现在2次FTS,速度提高100万倍。

00:19:00 SQL> select a.*,b.* from temp a,temp b
00:19:06 2 where a.id=b.id+1 and a.START_IP_NUMBER<=b.END_IP_NUMBER;

no rows selected

Elapsed: 00:00:16.06

Eagle Fan oracle

  1. January 2nd, 2009 at 19:43 | #1

    好帖,但是标题有些误导

    在实际的应用中,恐怕这个临时表的维护要通过应用去走吧? 本身就难保证新的纪录近来符合你的排序要求的

  2. 棉花糖ONE
    January 2nd, 2009 at 23:05 | #2

    按照你重叠的定义语句应该也可以先成这样吧
    假设ip是192.168.0.1
    算出第三个小数点之前的位数,包含小数点,这里是10

    select /*+ use_hash(a,b) */ a.* from test1 a,test2 b
    where a.start_ip>b.start_ip and a.start_ip

  3. 棉花糖ONE
    January 2nd, 2009 at 23:06 | #3

    怎么显示不全啊

  4. 棉花糖ONE
    January 2nd, 2009 at 23:12 | #4

    类似sql
    select /*+ use_hash(t1,t2) */
    t1.* from
    test t1,test t2 where t1.a>t2.a and t1.a

  5. January 3rd, 2009 at 02:07 | #5

    还是要从应用逻辑入手,能够得到更大的优化空间.

    我在2002年处理电信数据时,匹配电话区号,用过类似的手段.
    不过我的处理更像 Sort-Merge join,也是两个Full table scan搞定,省了临时Staging table.

  6. hoterran
    January 3rd, 2009 at 21:09 | #6

    初看了一下,是不是一个lag ,单表就可以了?

  7. January 4th, 2009 at 10:47 | #7

    对头, SQL Analytic Function Lead() Lag() 都行, 或者 SQL Model.
    改天我写个测试案例 + Benchmark.

  8. January 4th, 2009 at 22:16 | #8

    To Fenng: 是有标题党的嫌疑了,不过现在不是兴这个嘛 :em06: .这个SQL是检测用的,只是上线前跑一次,所以当时只是解决了就好.如果是实际应用的话,
    应该是要应用自己维护这个临时表的
    To Hoterran 木匠: 分析函数用的不多,能够给出SQL吗? 这样不同的列之间比较也可以用吗?
    To 棉花糖:你是不是有什么特殊字符被截断了?

  9. big bear
    January 5th, 2009 at 11:15 | #9

    如果我加一个START_IP_NUMBER,END_IP_NUMBER的组合索引呢?或者START_IP_NUMBER,END_IP_NUMBER DESC呢?

  10. January 5th, 2009 at 11:20 | #10

    下周我找时间写一个详细测试案例,敬请期待,stay tune. ^_^

  11. Eagle Fan
    January 5th, 2009 at 15:46 | #11

    组合索引看起来对这个案例没什么作用。期待木匠的测试了

  12. January 8th, 2009 at 14:11 | #12
  1. No trackbacks yet.