I recently encountered a situation where a Function-Based Index would be disabled and there was not a known reason. This particular FBI performs a query against another table (which is actually a violation of the DETERMINISTIC requirement but was still allowed). So I set up a small test trying to find the reason an FBI would be disabled.
Good news - in the test I performed I did not disable an index
Bad news - since it did not, I don't know what disabled the index
Really, REALLY BAD NEWS - using a query in a function-based index opens the door to incorrect results.
I show my work at the bottom of the blog so you can see exactly what is happening. There are a few more conditions I need to test (index rebuild, disable/reenable the index) and if you have other ideas, suggestions, please include them in your comments.
I created two tables, t_emp & t_dept, that were copies of emp & dept, respectively. There are not any integrity constraints (other than not null) nor indexes. I then created a function that accepted the department number (deptno) and returned the department name (dname). This mirrors the function that DAFY is using for the disabled FBI.
When I created the index, the index value is the t_dept.dname. Here is a list of some of the tests and the impact on the index.
1) Insert a record into t_emp with a deptno value not in t_dept - no error and FBI is enabled.
2) Delete a record from t_dept that has records with the same deptno value in t_emp - no error and FBI is enabled.
3) Truncate t_dept - no error and FBI is enabled.
4) Recreate t_dept as in the original process - no error and FBI is enabled.
I did not find a triggering event to disable the index. However, I found something I consider more of a problem.
When updating the dname in t_dept, the FBI is not updated. This means that incorrect data is returned. If a new record is inserted, then the FBI value reflects the new dname. If the deptno value in t_emp is updated...the FBI is *NOT* updated! Querying the t_emp table using the function created, which is the way FBIs are intended to be used, uses the FBI and returns what are now incorrect results.
SQL> create table t_emp as select * from emp;
SQL> create table t_dept as select * from dept;
SQL> CREATE OR REPLACE FUNCTION get_dept_name
2 ( p_deptno IN t_emp.deptno%TYPE)
3 RETURN t_dept.dname%TYPE
4 DETERMINISTIC
5 IS
6 l_dname t_dept.dname%TYPE;
7 CURSOR c_dname IS
8 SELECT dname
9 FROM t_dept
10 WHERE deptno = p_deptno;
11 BEGIN
12 OPEN c_dname;
13 FETCH c_dname INTO l_dname;
14 CLOSE c_dname;
15 RETURN l_dname;
16 END get_dept_name;
17 /
SQL> show error
No errors.
SQL> CREATE INDEX ix_dname ON t_emp (get_dept_name(deptno))
2 /
SQL> SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> select * from t_dept;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> insert into t_emp (empno, deptno) values (1000, 10);
SQL> commit;
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> insert into t_emp (empno, deptno) values (1001, 50)
2 /
SQL> commit;
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> delete from t_dept where deptno = 10;
SQL> commit;
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> select * from t_emp;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17-DEC-80 800 20
7499 ALLEN SALESMAN 7698 20-FEB-81 1600 300 30
7521 WARD SALESMAN 7698 22-FEB-81 1250 500 30
7566 JONES MANAGER 7839 02-APR-81 2975 20
7654 MARTIN SALESMAN 7698 28-SEP-81 1250 1400 30
7698 BLAKE MANAGER 7839 01-MAY-81 2850 30
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7788 SCOTT ANALYST 7566 09-DEC-82 3000 20
7839 KING PRESIDENT 17-NOV-81 5000 10
7844 TURNER SALESMAN 7698 08-SEP-81 1500 0 30
7876 ADAMS CLERK 7788 12-JAN-83 1100 20
7900 JAMES CLERK 7698 03-DEC-81 950 30
7902 FORD ANALYST 7566 03-DEC-81 3000 20
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
1001 50
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> truncate table t_dept;
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> drop table t_dept;
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> create table t_dept as select * from dept;
SQL> SELECT index_name, index_type, funcidx_status
2 FROM user_indexes
3 WHERE table_name = 'T_EMP';
INDEX_NAME INDEX_TYPE FUNCIDX_
------------------------------ --------------------------- --------
IX_DNAME FUNCTION-BASED NORMAL ENABLED
SQL> select * from t_emp where get_dept_name(deptno) = 'ACCOUNTING'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7839 KING PRESIDENT 17-NOV-81 5000 10
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
SQL> update t_dept set dname = 'TEST' where deptno = 10;
SQL> commit;
SQL> select * from t_emp where get_dept_name(deptno) = 'ACCOUNTING'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7839 KING PRESIDENT 17-NOV-81 5000 10
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
SQL> insert into t_emp (empno, deptno) values (1002, 10);
SQL> commit
2 /
SQL> select * from t_emp where get_dept_name(deptno) = 'ACCOUNTING'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7839 KING PRESIDENT 17-NOV-81 5000 10
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
SQL> select * from t_emp where get_dept_name(deptno) = 'TEST'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
1002 10
SQL> update t_emp set deptno = deptno;
SQL> select * from t_emp where get_dept_name(deptno) = 'ACCOUNTING'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7839 KING PRESIDENT 17-NOV-81 5000 10
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
SQL> update t_emp set deptno = 10 where deptno = 10;
SQL> select * from t_emp where get_dept_name(deptno) = 'ACCOUNTING'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7839 KING PRESIDENT 17-NOV-81 5000 10
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
SQL> set autotrace on explain
SQL> select * from t_emp where get_dept_name(deptno) = 'ACCOUNTING'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09-JUN-81 2450 10
7839 KING PRESIDENT 17-NOV-81 5000 10
7934 MILLER CLERK 7782 23-JAN-82 1300 10
1000 10
Execution Plan
----------------------------------------------------------
Plan hash value: 4230458767
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 87 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_EMP | 1 | 87 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_DNAME | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SCOTT"."GET_DEPT_NAME"("DEPTNO")='ACCOUNTING')
SQL> select * from t_emp where get_dept_name(deptno) = 'TEST'
2 /
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- --------- ---------- ---------- ----------
1002 10
Execution Plan
----------------------------------------------------------
Plan hash value: 4230458767
----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 87 | 2 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| T_EMP | 1 | 87 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IX_DNAME | 1 | | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SCOTT"."GET_DEPT_NAME"("DEPTNO")='TEST')
Note
-----
- dynamic sampling used for this statement
Tuesday, September 23, 2008
Subscribe to:
Post Comments (Atom)
3 comments:
Hmmm, interesting.
I'm sure there is a good reason for having such a strange FBI :)
Kind-of like a bitmap join index but without the bitmap.
>there was not a known reason
I have seen a similar situation with a release script where it was always doing a dbms_utility.compile_schema at the end.
I'm surprised that you would expect the FBI to be updated. A materialized view might have been a more appropriate structure.
The DETERMINISTIC keyword indicates that the function will return a consistent result given a certain input. Therefore if you updated one of the rows in the table that the FBI is on and changed one of the inputs then you would expect the fbi to be re-evaluated. However the table lookup is bound to violate this given.
One could argue that the combination of lookup and DETERMINISTIC keyword could be objected to at compilation time. But given that it's not, the behaviour of the function seems appropriate albeit for an inappropriate implementation.
Post a Comment