排序sql查询结果的性能问题

时间:2013-12-02 09:52:33

标签: sql performance oracle

我在sql中不是很好,所以我在排序方面遇到了一些问题。 我有一个表格,其中包含一些地点的地理坐标。我需要找到10个最近的位置并显示它们。所以为此,我使用了这样的SQL查询:

SELECT 
      6373*2*ATAN2(
      POWER(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2) , 0.5), 
      POWER(1-(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2)),0.5)) AS d
FROM
      (SELECT
             (f.loc_lat)*3.1415/180 AS f_lat, (f.loc_lon)*3.1415/180 AS f_lon,
             (s.loc_lat)*3.1415/180 AS s_lat, (s.loc_lon)*3.1415/180 AS s_lon,
             (s.loc_lat)*3.1415/180 - (f.loc_lat)*3.1415/180 AS lat, 
             (s.loc_lon)*3.1415/180 - (f.loc_lon)*3.1415/180 AS lon
      FROM htl f, htl s 
      WHERE f.htl_cd != s.htl_cd 
      AND f.loc_lat is not null
      AND f.loc_lon is not null
      AND f.ctry_cd = s.ctry_cd
      ) location
)order by d asc;

如果我在没有最后一行的情况下运行此查询,一切都很好。但是,当我想要排序结果时,我有问题。对我来说排序很长(接近30分钟)。

查询次数接近2800万行。

所以我可能需要重写我的查询,或者你可能知道一些提示来加快它的速度。

更新

表htl中的

数据如下所示:

htl_cd           loc_lat         loc_lon

GDLUA           20.690688        -103.416281

LPLRL           53.463509        -2.876497

BDLWL           41.925992        -72.670218

MKCGM           38.892569        -94.523921

第一列是简单的主键。 第二和第三列是纬度和经度(坐标)

6373*2*ATAN2(
          POWER(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2) , 0.5), 
          POWER(1-(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2)),0.5)) AS d

这个公式用于计算两个位置之间的距离

UPDATE2 对于需要执行计划的人来说

执行计划

计划哈希值:446529610

----------------------------------------------- ---------------------------------

| Id |操作|名称|行|字节| TempSpc |成本(%CPU)|时间    |

----------------------------------------------- ---------------------------------

| 0 |选择声明| | 12M | 733M | | 186K(2)| 00:37: 22 |

| 1 |排序顺序| | 12M | 733M | 1771M | 186K(2)| 00:37: 22 |

| * 2 | HASH JOIN | | 12M | 733M | | 259(84)| 00:00: 04 |

| * 3 |表访问完全| HTL | 5081 | 148K | | 22(0)| 00:00: 01 |

| 4 |表访问完全| HTL | 5411 | 158K | | 22(0)| 00:00: 01 |

----------------------------------------------- ---------------------------------

谓词信息(由操作ID标识):

   2 - access("F"."CTRY_CD"="S"."CTRY_CD")
       filter("F"."HTL_CD"<>"S"."HTL_CD")
   3 - filter("F"."LOC_LAT" IS NOT NULL)

统计

        537  recursive calls
         18  db block gets
        152  consistent gets
      68534  physical reads
          0  redo size
  463630284  bytes sent via SQL*Net to client
    9504847  bytes received via SQL*Net from client
     864044  SQL*Net roundtrips to/from client
          0  sorts (memory)
          1  sorts (disk)
   12960638  rows processed

3 个答案:

答案 0 :(得分:2)

  
    

如果我在没有最后一行的情况下运行此查询,一切都很好。

  

您的意思是您是从查询中快速返回第一行,还是快速返回整组2800万行?您的查询非常耗费计算量。

  
    

但是当我想对结果进行排序时,我有问题。对我来说排序很长(接近30分钟)。

  

排序要求计算所有值,并且在返回任何行之前对它们进行排序。如果所有行都是从未排序的查询中快速返回的,则排序阶段需要很长时间,而可能的原因是排序正在溢出到临时磁盘。您可以在查询运行时监视v $ sql_workarea_active视图所需的排序空间,这将告诉您需要分配多少内存以避免使用临时磁盘空间。

您还可以使用gather_plan_statistics提示和DBMS_XPlan准确地分析执行计划中每个阶段的时间。 http://docs.oracle.com/cd/E11882_01/appdev.112/e25788/d_xplan.htm#ARPLS70137

最后,你真的需要对所有2800万行进行排序吗?你需要知道最远的点,或者最接近的点吗?如果是后者,也许您可​​以考虑在ORDER BY之前过滤结果集。

答案 1 :(得分:2)

您的查询返回每个距离两次。如果您有最接近的行ab,那么它会返回abba的距离。只需比较两种排列之一的距离,就可以消除一半的计算:

SQL Fiddle

Oracle 11g R2架构设置

CREATE TABLE htl ( htl_cd, loc_lat, loc_lon, ctry_cd ) AS
          SELECT 'GDLUA', 20.690688, -103.416281, 1 FROM DUAL
UNION ALL SELECT 'LPLRL', 53.463509,   -2.876497, 1 FROM DUAL
UNION ALL SELECT 'BDLWL', 41.925992,  -72.670218, 1 FROM DUAL
UNION ALL SELECT 'MKCGM', 38.892569,  -94.523921, 1 FROM DUAL
/

BEGIN
  FOR i IN 1 .. 20 LOOP
    INSERT INTO htl VALUES(
      'GD' || LPAD( TO_CHAR( i ), 3, '0' ),
      20.690688 + i / 1000,
      -103.416281 + i / 1000,
      1
    );
  END LOOP;
END;
/

查询1

WITH indexed_locations AS (
  SELECT h.*,
         ROWNUM AS idx
  FROM   htl h
),
location AS (
  SELECT
          (f.loc_lat)*3.1415/180 AS f_lat, (f.loc_lon)*3.1415/180 AS f_lon,
          (s.loc_lat)*3.1415/180 AS s_lat, (s.loc_lon)*3.1415/180 AS s_lon,
          (s.loc_lat)*3.1415/180 - (f.loc_lat)*3.1415/180 AS lat, 
          (s.loc_lon)*3.1415/180 - (f.loc_lon)*3.1415/180 AS lon
  FROM    indexed_locations f
          INNER JOIN
          indexed_locations s
          ON (
                  f.htl_cd  != s.htl_cd 
              AND f.ctry_cd  = s.ctry_cd
              AND f.idx      < s.idx
             )
  WHERE
          f.loc_lat is not null
  AND     f.loc_lon is not null

),
distances AS (
  SELECT 
        6373*2*ATAN2(
        POWER(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2) , 0.5), 
        POWER(1-(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2)),0.5)) AS d
  FROM  location
  ORDER BY d ASC
)
SELECT d
FROM   distances
WHERE  ROWNUM < 11

<强> Results

|              D |
|----------------|
| 0.152300994984 |
| 0.152301463917 |
| 0.152301932829 |
| 0.152302401722 |
| 0.152302870595 |
| 0.152303339447 |
|  0.15230380828 |
| 0.152304277093 |
| 0.152304745885 |
| 0.152305214658 |

您还可以将所有计算移动到用户定义的函数中并生成函数DETERMINISTIC(如果使用相同的输入参数将有所帮助) - 但是,请注意在分析它时运行一次然后可以缓存结果,后续运行可以使用缓存的结果,这样您每次都不会看到查询的真实成本:

CREATE OR REPLACE FUNCTION calc_Lat_Long_Distance(
  f_loc_lat HTL.LOC_LAT%TYPE,
  f_loc_lon HTL.LOC_LON%TYPE,
  s_loc_lat HTL.LOC_LAT%TYPE,
  s_loc_lon HTL.LOC_LON%TYPE
) RETURN NUMBER DETERMINISTIC
AS
  PI    CONSTANT REAL := 3.1415/180;
  f_lat       REAL := f_loc_lat * PI;
  f_lon       REAL := f_loc_lon * PI;
  s_lat       REAL := s_loc_lat * PI;
  s_lon       REAL := s_loc_lon * PI;
  lat_dif     REAL := s_lat - f_lat;
  lon_dif     REAL := s_lon - f_lon;
  p           REAL := POWER(SIN(lat_dif/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon_dif/2),2);
BEGIN
  RETURN 6373*2*ATAN2( POWER(p, 0.5), POWER(1-p,0.5) );
END;
/

您也可以使用RESULT_CACHE代替DETERMINISTIC - 对其进行分析,看看是否会产生特殊差异。

查询2

WITH indexed_locations AS (
  SELECT h.*,
         ROWNUM AS idx
  FROM   htl h
),
distances AS (
  SELECT  calc_Lat_Long_Distance(
            f.loc_lat,
            f.loc_lon,
            s.loc_lat,
            s.loc_lon
          ) AS d
  FROM    indexed_locations f
          INNER JOIN
          indexed_locations s
          ON (
                  f.htl_cd  != s.htl_cd 
              AND f.ctry_cd = s.ctry_cd
              AND f.idx     < s.idx
             )
  WHERE
          f.loc_lat is not null
  AND     f.loc_lon is not null
  ORDER BY d ASC
)
SELECT d
FROM   distances
WHERE  ROWNUM < 11

<强> Results

|              D |
|----------------|
| 0.152300994984 |
| 0.152301463917 |
| 0.152301932829 |
| 0.152302401722 |
| 0.152302870595 |
| 0.152303339447 |
|  0.15230380828 |
| 0.152304277093 |
| 0.152304745885 |
| 0.152305214658 |

答案 2 :(得分:0)

SELECT * FROM (
      SELECT 
            6373*2*ATAN2(
            POWER(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2) , 0.5), 
            POWER(1-(POWER(SIN(lat/2),2) + COS(f_lat)*COS(s_lat)*POWER(SIN(lon/2),2)),0.5)) AS d,
            ROW_NUMBER() OVER (ORDER BY d asc) AS rownum
      FROM
            (SELECT
                   (f.loc_lat)*3.1415/180 AS f_lat, (f.loc_lon)*3.1415/180 AS f_lon,
                   (s.loc_lat)*3.1415/180 AS s_lat, (s.loc_lon)*3.1415/180 AS s_lon,
                   (s.loc_lat)*3.1415/180 - (f.loc_lat)*3.1415/180 AS lat, 
                   (s.loc_lon)*3.1415/180 - (f.loc_lon)*3.1415/180 AS lon
            FROM htl f, htl s 
            WHERE f.htl_cd != s.htl_cd 
            AND f.loc_lat is not null
            AND f.loc_lat is not null
            AND f.ctry_cd = s.ctry_cd
            ) location
)WHERE rownum <= 10;

如果您只需要前10条记录,请尝试以上。