DB2优化器深入理解案例 - 不恰当的操作产生错误的查询结果

构造实验环境

--创建2个表
CREATE TABLE STORE (STORE_ID INT NOT NULL PRIMARY KEY, LOCATION CHAR(10))
CREATE TABLE SALES (STORE_ID INT NOT NULL, TOTAL_SALE INT, DY TIMESTAMP DEFAULT CURRENT TIMESTAMP)
--建立外键关联
ALTER TABLE "SALES" ADD CONSTRAINT "FK_SALES_STORE" FOREIGN KEY ("STORE_ID") REFERENCES "STORE" ("STORE_ID") ON DELETE NO ACTION ON UPDATE NO ACTION NOT ENFORCED ENABLE QUERY OPTIMIZATION
--向父表插入数据
INSERT INTO STORE VALUES (1,'BRAMPTON'),(2,'MARKHAM'),(3,'MISSI'),(4,'TORONTO'),(5,'BOLTON')
--向子表load数据,注意这里的最后一行是不符合外键约束的
$ cat sales_week.del
1,10000,"2012-02-01-12.15.00.410005"
1,15000,"2012-02-02-12.15.01.120003"
1,17500,"2012-02-03-12.15.05.920001"
10,20000,"2012-02-04-12.15.03.222211"
$
$ db2 "LOAD FROM sales_week.del OF DEL INSERT INTO SALES NONRECOVERABLE"
SQL3109N  The utility is beginning to load data from file
"/tmp/sales_week.del".

SQL3500W  The utility is beginning the "LOAD" phase at time "12/27/2016
19:09:09.949565".

SQL3519W  Begin Load Consistency Point. Input record count = "0".

SQL3520W  Load Consistency Point was successful.

SQL3110N  The utility has completed processing.  "4" rows were read from the
input file.

SQL3519W  Begin Load Consistency Point. Input record count = "4".

SQL3520W  Load Consistency Point was successful.

SQL3515W  The utility has finished the "LOAD" phase at time "12/27/2016
19:09:09.976620".


Number of rows read         = 4
Number of rows skipped      = 0
Number of rows loaded       = 4
Number of rows rejected     = 0
Number of rows deleted      = 0
Number of rows committed    = 4

$

-- 在这里不做数据完整性检查
db2 "SET INTEGRITY FOR SALES CHECK IMMEDIATE UNCHECKED"

-- 外键约束已经检查过了
$ db2 "SELECT TABSCHEMA, TABNAME, STATUS, ACCESS_MODE, SUBSTR(CONST_CHECKED,1,1) AS FK_CHECKED FROM SYSCAT.TABLES WHERE TABNAME='SALES'"

TABSCHEMA     TABNAME  STATUS ACCESS_MODE FK_CHECKED
------------- -------- ------ ----------- ----------
DB2INST1      SALES    N      F           Y         

  1 record(s) selected.
 
奇怪的地方到了,下面的查询返回3行
$ db2 "SELECT * FROM STORE S, SALES SL WHERE S.STORE_ID=SL.STORE_ID"

STORE_ID    LOCATION   STORE_ID    TOTAL_SALE  DY                        
----------- ---------- ----------- ----------- --------------------------
          1 BRAMPTON             1       10000 2012-02-01-12.15.00.410005
          1 BRAMPTON             1       15000 2012-02-02-12.15.01.120003
          1 BRAMPTON             1       17500 2012-02-03-12.15.05.920001

  3 record(s) selected.
$
这个查询返回4行数据
$ db2 "SELECT COUNT(*) AS CNT FROM STORE S, SALES SL WHERE S.STORE_ID=SL.STORE_ID"

CNT        
-----------
          4

  1 record(s) selected.

$
这是为什么那?分析执行计划可以找到原因。

首先编写一个简单shell脚本来生成执行计划
$ cat explain.sh
#!/usr/bin/ksh
if [ -f /home/db2inst1/sqllib/db2profile ]; then
    . /home/db2inst1/sqllib/db2profile
fi
sqlname=$1
db2 connect to SAMPLE
db2 set current explain mode explain
db2 -tvf ${sqlname}.sql
db2 set current explain mode no
db2exfmt -d SAMPLE -1 -o ${sqlname}.out
db2 terminate
$

第一个查询的执行计划
Original Statement:
------------------
SELECT
  *
FROM
  STORE S,
  SALES SL
WHERE
  S.STORE_ID=SL.STORE_ID


Optimized Statement:
-------------------
SELECT
  Q1.STORE_ID AS "STORE_ID",
  Q2.LOCATION AS "LOCATION",
  Q1.STORE_ID AS "STORE_ID",
  Q1.TOTAL_SALE AS "TOTAL_SALE",
  Q1.DY AS "DY"
FROM
  DB2INST1.SALES AS Q1,
  DB2INST1.STORE AS Q2
WHERE
  (Q2.STORE_ID = Q1.STORE_ID)

Access Plan:
-----------
        Total Cost:             20.3334
        Query Degree:           1

              Rows
             RETURN
             (   1)
              Cost
               I/O
               |
                4
             ^HSJOIN
             (   2)
             20.3334
                3
         /-----+------\
        4                5
     TBSCAN           TBSCAN
     (   3)           (   4)
     13.5505          6.78227
        2                1
       |                |
        4                5
 TABLE: DB2INST1  TABLE: DB2INST1
      SALES            STORE
       Q1               Q2
       
第二个查询的执行计划
Original Statement:
------------------
SELECT
  COUNT(*) AS CNT
FROM
  STORE S,
  SALES SL
WHERE
  S.STORE_ID=SL.STORE_ID


Optimized Statement:
-------------------
SELECT
  Q3.$C0 AS "CNT"
FROM
  (SELECT
     COUNT(*)
   FROM
     (SELECT
        $RID$
      FROM
        DB2INST1.SALES AS Q1
     ) AS Q2
  ) AS Q3

Access Plan:
-----------
        Total Cost:             13.5511
        Query Degree:           1

      Rows
     RETURN
     (   1)
      Cost
       I/O
       |
        1
     GRPBY
     (   2)
     13.5508
        2
       |
        4
     TBSCAN
     (   3)
     13.5505
        2
       |
        4
 TABLE: DB2INST1
      SALES
       Q1


由于前面使用了IMMEDIATE UNCHECKED放弃参照完整性检查,并且确实在子表中有一行违反了参照完整性。对于第二个查询来说,因为两个表之间是父表和子表的关系,
所以2表关联求返回行数,只是查看一下子表的行数就可以了,DB2优化器依据此原理,自动忽略了父表,从而这变成了一个简单的单表的查询,返回4行。
对于第一个查询而言,因为要查2表的所有的列,所以,2表还是做了关联查询,把违反参照完整性的那行给过滤掉了。

结论:我们可能有时候使用了IMMEDIATE UNCHECKED作为一个中间的处理步骤,放弃了参照完整性检查,但是最后还是要检查完整性的,不然可能有些查询要出错。

请使用浏览器的分享功能分享到微信等