1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-11-14 00:22:38 +03:00

Experimental opimizations to speed up FK constraint CASCADE and SET NULL action processing.

FossilOrigin-Name: 8c5dd6cc259e0cdaaddaa52ccfa96fee6b166906
This commit is contained in:
dan
2014-12-17 15:03:50 +00:00
5 changed files with 183 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
C Fix\sthe\se_walauto.test\sscript\sso\sthat\sit\sworks\son\swindows. C Experimental\sopimizations\sto\sspeed\sup\sFK\sconstraint\sCASCADE\sand\sSET\sNULL\saction\sprocessing.
D 2014-12-16T12:46:38.635 D 2014-12-17T15:03:50.611
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in 6c4f961fa91d0b4fa121946a19f9e5eac2f2f809 F Makefile.in 6c4f961fa91d0b4fa121946a19f9e5eac2f2f809
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
@@ -184,7 +184,7 @@ F src/date.c 93594514aae68de117ca4a2a0d6cc63eddf26744
F src/delete.c 0750b1eb4d96cd3fb2c798599a3a7c85e92f1417 F src/delete.c 0750b1eb4d96cd3fb2c798599a3a7c85e92f1417
F src/expr.c 00da3072f362b06f39ce4052baa1d4ce2bb36d1c F src/expr.c 00da3072f362b06f39ce4052baa1d4ce2bb36d1c
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
F src/fkey.c da985ae673efef2c712caef825a5d2edb087ead7 F src/fkey.c e0444b61bed271a76840cbe6182df93a9baa3f12
F src/func.c 6d3c4ebd72aa7923ce9b110a7dc15f9b8c548430 F src/func.c 6d3c4ebd72aa7923ce9b110a7dc15f9b8c548430
F src/global.c 6ded36dda9466fc1c9a3c5492ded81d79bf3977d F src/global.c 6ded36dda9466fc1c9a3c5492ded81d79bf3977d
F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5 F src/hash.c 4263fbc955f26c2e8cdc0cf214bc42435aa4e4f5
@@ -295,7 +295,7 @@ F src/vdbe.c 1a9e671c9cfc259e4d2affc71f7df4a4c00a842c
F src/vdbe.h 6fc69d9c5e146302c56e163cb4b31d1ee64a18c3 F src/vdbe.h 6fc69d9c5e146302c56e163cb4b31d1ee64a18c3
F src/vdbeInt.h 9bb69ff2447c34b6ccc58b34ec35b615f86ead78 F src/vdbeInt.h 9bb69ff2447c34b6ccc58b34ec35b615f86ead78
F src/vdbeapi.c 4bc511a46b9839392ae0e90844a71dc96d9dbd71 F src/vdbeapi.c 4bc511a46b9839392ae0e90844a71dc96d9dbd71
F src/vdbeaux.c 6f7f39c3fcf0f5923758df8561bb5d843908a553 F src/vdbeaux.c 07ef87c6d4b5abdf13ff33babb10205702fdab0a
F src/vdbeblob.c 4af4bfb71f6df7778397b4a0ebc1879793276778 F src/vdbeblob.c 4af4bfb71f6df7778397b4a0ebc1879793276778
F src/vdbemem.c 31d8eabb0cd78bfeab4e5124c7363c3e9e54db9f F src/vdbemem.c 31d8eabb0cd78bfeab4e5124c7363c3e9e54db9f
F src/vdbesort.c c150803a3e98fbc68bd07772cbbd4328a0a7212d F src/vdbesort.c c150803a3e98fbc68bd07772cbbd4328a0a7212d
@@ -504,6 +504,7 @@ F test/fkey4.test 86446017011273aad8f9a99c1a65019e7bd9ca9d
F test/fkey5.test 8a1fde4e7721ae00b05b3178888833726ca2df8d F test/fkey5.test 8a1fde4e7721ae00b05b3178888833726ca2df8d
F test/fkey6.test abb59f866c1b44926fd02d1fdd217d831fe04f48 F test/fkey6.test abb59f866c1b44926fd02d1fdd217d831fe04f48
F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13 F test/fkey7.test 72e915890ee4a005daaf3002cb208e8fe973ac13
F test/fkey8.test 8f08203458321e6c19a263829de4cfc936274ab0
F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749 F test/fkey_malloc.test 594a7ea1fbab553c036c70813cd8bd9407d63749
F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb F test/format4.test 1f0cac8ff3895e9359ed87e41aaabee982a812eb
F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c F test/fts-9fd058691.test 78b887e30ae6816df0e1fed6259de4b5a64ad33c
@@ -1232,7 +1233,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32 F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
P ae43539e62e76676a3daf561b629a1b9b4e2d2c9 P 7d092ebb6724c3c0fdc05dc94ca767d158933fb5 210cb2a6aaf780365064a26c0c99926bd6346e19
R 6cb5309eecfa4835aff26790e68b0f80 R 2edc2fe620223f33e078e9395972a943
U drh U dan
Z 6fc407d3934d070a5bb755e43a0ba0ad Z c2959ecd9d3a1afd4baaa55723226ea7

View File

@@ -1 +1 @@
7d092ebb6724c3c0fdc05dc94ca767d158933fb5 8c5dd6cc259e0cdaaddaa52ccfa96fee6b166906

View File

@@ -437,7 +437,7 @@ static void fkLookupParent(
OE_Abort, 0, P4_STATIC, P5_ConstraintFK); OE_Abort, 0, P4_STATIC, P5_ConstraintFK);
}else{ }else{
if( nIncr>0 && pFKey->isDeferred==0 ){ if( nIncr>0 && pFKey->isDeferred==0 ){
sqlite3ParseToplevel(pParse)->mayAbort = 1; sqlite3MayAbort(pParse);
} }
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
} }
@@ -509,6 +509,10 @@ static Expr *exprTableColumn(
** code for an SQL UPDATE operation, this function may be called twice - ** code for an SQL UPDATE operation, this function may be called twice -
** once to "delete" the old row and once to "insert" the new row. ** once to "delete" the old row and once to "insert" the new row.
** **
** Parameter nIncr is passed -1 when inserting a row (as this may decrease
** the number of FK violations in the db) or +1 when deleting one (as this
** may increase the number of FK constraint problems).
**
** The code generated by this function scans through the rows in the child ** The code generated by this function scans through the rows in the child
** table that correspond to the parent table row being deleted or inserted. ** table that correspond to the parent table row being deleted or inserted.
** For each child row found, one of the following actions is taken: ** For each child row found, one of the following actions is taken:
@@ -625,13 +629,9 @@ static void fkScanChildren(
sqlite3ResolveExprNames(&sNameContext, pWhere); sqlite3ResolveExprNames(&sNameContext, pWhere);
/* Create VDBE to loop through the entries in pSrc that match the WHERE /* Create VDBE to loop through the entries in pSrc that match the WHERE
** clause. If the constraint is not deferred, throw an exception for ** clause. For each row found, increment either the deferred or immediate
** each row found. Otherwise, for deferred constraints, increment the ** foreign key constraint counter. */
** deferred constraint counter by nIncr for each row selected. */
pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0); pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0, 0, 0, 0);
if( nIncr>0 && pFKey->isDeferred==0 ){
sqlite3ParseToplevel(pParse)->mayAbort = 1;
}
sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr); sqlite3VdbeAddOp2(v, OP_FkCounter, pFKey->isDeferred, nIncr);
if( pWInfo ){ if( pWInfo ){
sqlite3WhereEnd(pWInfo); sqlite3WhereEnd(pWInfo);
@@ -810,6 +810,24 @@ static int fkParentIsModified(
return 0; return 0;
} }
/*
** Return true if the parser passed as the first argument is being
** used to code a trigger that is really a "SET NULL" action belonging
** to trigger pFKey.
*/
static int isSetNullAction(Parse *pParse, FKey *pFKey){
Parse *pTop = sqlite3ParseToplevel(pParse);
if( pTop->pTriggerPrg ){
Trigger *p = pTop->pTriggerPrg->pTrigger;
if( (p==pFKey->apTrigger[0] && pFKey->aAction[0]==OE_SetNull)
|| (p==pFKey->apTrigger[1] && pFKey->aAction[1]==OE_SetNull)
){
return 1;
}
}
return 0;
}
/* /*
** This function is called when inserting, deleting or updating a row of ** This function is called when inserting, deleting or updating a row of
** table pTab to generate VDBE code to perform foreign key constraint ** table pTab to generate VDBE code to perform foreign key constraint
@@ -862,7 +880,7 @@ void sqlite3FkCheck(
int *aiCol; int *aiCol;
int iCol; int iCol;
int i; int i;
int isIgnore = 0; int bIgnore = 0;
if( aChange if( aChange
&& sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0 && sqlite3_stricmp(pTab->zName, pFKey->zTo)!=0
@@ -921,7 +939,7 @@ void sqlite3FkCheck(
int rcauth; int rcauth;
char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName; char *zCol = pTo->aCol[pIdx ? pIdx->aiColumn[i] : pTo->iPKey].zName;
rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb); rcauth = sqlite3AuthReadCol(pParse, pTo->zName, zCol, iDb);
isIgnore = (rcauth==SQLITE_IGNORE); bIgnore = (rcauth==SQLITE_IGNORE);
} }
#endif #endif
} }
@@ -936,12 +954,18 @@ void sqlite3FkCheck(
/* A row is being removed from the child table. Search for the parent. /* A row is being removed from the child table. Search for the parent.
** If the parent does not exist, removing the child row resolves an ** If the parent does not exist, removing the child row resolves an
** outstanding foreign key constraint violation. */ ** outstanding foreign key constraint violation. */
fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1,isIgnore); fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regOld, -1, bIgnore);
} }
if( regNew!=0 ){ if( regNew!=0 && !isSetNullAction(pParse, pFKey) ){
/* A row is being added to the child table. If a parent row cannot /* A row is being added to the child table. If a parent row cannot
** be found, adding the child row has violated the FK constraint. */ ** be found, adding the child row has violated the FK constraint.
fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1,isIgnore); **
** If this operation is being performed as part of a trigger program
** that is actually a "SET NULL" action belonging to this very
** foreign key, then omit this scan altogether. As all child key
** values are guaranteed to be NULL, it is not possible for adding
** this row to cause an FK violation. */
fkLookupParent(pParse, iDb, pTo, pIdx, pFKey, aiCol, regNew, +1, bIgnore);
} }
sqlite3DbFree(db, aiFree); sqlite3DbFree(db, aiFree);
@@ -962,8 +986,8 @@ void sqlite3FkCheck(
&& !pParse->pToplevel && !pParse->isMultiWrite && !pParse->pToplevel && !pParse->isMultiWrite
){ ){
assert( regOld==0 && regNew!=0 ); assert( regOld==0 && regNew!=0 );
/* Inserting a single row into a parent table cannot cause an immediate /* Inserting a single row into a parent table cannot cause (or fix)
** foreign key violation. So do nothing in this case. */ ** an immediate foreign key violation. So do nothing in this case. */
continue; continue;
} }
@@ -987,13 +1011,28 @@ void sqlite3FkCheck(
fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1); fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regNew, -1);
} }
if( regOld!=0 ){ if( regOld!=0 ){
/* If there is a RESTRICT action configured for the current operation int eAction = pFKey->aAction[aChange!=0];
** on the parent table of this FK, then throw an exception
** immediately if the FK constraint is violated, even if this is a
** deferred trigger. That's what RESTRICT means. To defer checking
** the constraint, the FK should specify NO ACTION (represented
** using OE_None). NO ACTION is the default. */
fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1); fkScanChildren(pParse, pSrc, pTab, pIdx, pFKey, aiCol, regOld, 1);
/* If this is a deferred FK constraint, or a CASCADE or SET NULL
** action applies, then any foreign key violations caused by
** removing the parent key will be rectified by the action trigger.
** So do not set the "may-abort" flag in this case.
**
** Note 1: If the FK is declared "ON UPDATE CASCADE", then the
** may-abort flag will eventually be set on this statement anyway
** (when this function is called as part of processing the UPDATE
** within the action trigger).
**
** Note 2: At first glance it may seem like SQLite could simply omit
** all OP_FkCounter related scans when either CASCADE or SET NULL
** applies. The trouble starts if the CASCADE or SET NULL action
** trigger causes other triggers or action rules attached to the
** child table to fire. In these cases the fk constraint counters
** might be set incorrectly if any OP_FkCounter related scans are
** omitted. */
if( !pFKey->isDeferred && eAction!=OE_Cascade && eAction!=OE_SetNull ){
sqlite3MayAbort(pParse);
}
} }
pItem->zName = 0; pItem->zName = 0;
sqlite3SrcListDelete(db, pSrc); sqlite3SrcListDelete(db, pSrc);

View File

@@ -396,6 +396,7 @@ static Op *opIterNext(VdbeOpIter *p){
*/ */
int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
int hasAbort = 0; int hasAbort = 0;
int hasFkCounter = 0;
Op *pOp; Op *pOp;
VdbeOpIter sIter; VdbeOpIter sIter;
memset(&sIter, 0, sizeof(sIter)); memset(&sIter, 0, sizeof(sIter));
@@ -404,15 +405,17 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
while( (pOp = opIterNext(&sIter))!=0 ){ while( (pOp = opIterNext(&sIter))!=0 ){
int opcode = pOp->opcode; int opcode = pOp->opcode;
if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename if( opcode==OP_Destroy || opcode==OP_VUpdate || opcode==OP_VRename
#ifndef SQLITE_OMIT_FOREIGN_KEY
|| (opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1)
#endif
|| ((opcode==OP_Halt || opcode==OP_HaltIfNull) || ((opcode==OP_Halt || opcode==OP_HaltIfNull)
&& ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort)) && ((pOp->p1&0xff)==SQLITE_CONSTRAINT && pOp->p2==OE_Abort))
){ ){
hasAbort = 1; hasAbort = 1;
break; break;
} }
#ifndef SQLITE_OMIT_FOREIGN_KEY
if( opcode==OP_FkCounter && pOp->p1==0 && pOp->p2==1 ){
hasFkCounter = 1;
}
#endif
} }
sqlite3DbFree(v->db, sIter.apSub); sqlite3DbFree(v->db, sIter.apSub);
@@ -421,7 +424,7 @@ int sqlite3VdbeAssertMayAbort(Vdbe *v, int mayAbort){
** through all opcodes and hasAbort may be set incorrectly. Return ** through all opcodes and hasAbort may be set incorrectly. Return
** true for this case to prevent the assert() in the callers frame ** true for this case to prevent the assert() in the callers frame
** from failing. */ ** from failing. */
return ( v->db->mallocFailed || hasAbort==mayAbort ); return ( v->db->mallocFailed || hasAbort==mayAbort || hasFkCounter );
} }
#endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */ #endif /* SQLITE_DEBUG - the sqlite3AssertMayAbort() function */

106
test/fkey8.test Normal file
View File

@@ -0,0 +1,106 @@
# 2001 September 15
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
#
#***********************************************************************
# This file implements regression tests for SQLite library.
#
# This file implements tests for foreign keys.
#
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix fkey8
ifcapable {!foreignkey} {
finish_test
return
}
do_execsql_test 1.0 { PRAGMA foreign_keys = 1; }
foreach {tn use_stmt sql schema} {
1 1 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1);
}
2.1 0 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE);
}
2.2 0 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE SET NULL);
}
2.3 1 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE SET DEFAULT);
}
3 1 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE);
CREATE TRIGGER ct1 AFTER DELETE ON c1 BEGIN
INSERT INTO p1 VALUES('x');
END;
}
4 1 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
CREATE TABLE cc1(d REFERENCES c1);
}
5.1 0 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
CREATE TABLE cc1(d REFERENCES c1 ON DELETE CASCADE);
}
5.2 0 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
CREATE TABLE cc1(d REFERENCES c1 ON DELETE SET NULL);
}
5.3 1 "DELETE FROM p1" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON DELETE CASCADE, c PRIMARY KEY);
CREATE TABLE cc1(d REFERENCES c1 ON DELETE SET DEFAULT);
}
6.1 1 "UPDATE p1 SET a = ?" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON UPDATE SET NULL, c);
}
6.2 0 "UPDATE OR IGNORE p1 SET a = ?" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON UPDATE SET NULL, c);
}
6.3 1 "UPDATE OR IGNORE p1 SET a = ?" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b REFERENCES p1 ON UPDATE CASCADE, c);
}
6.4 1 "UPDATE OR IGNORE p1 SET a = ?" {
CREATE TABLE p1(a PRIMARY KEY);
CREATE TABLE c1(b NOT NULL REFERENCES p1 ON UPDATE SET NULL, c);
}
} {
drop_all_tables
do_test 1.$tn {
execsql $schema
set stmt [sqlite3_prepare_v2 db $sql -1 dummy]
set ret [uses_stmt_journal $stmt]
sqlite3_finalize $stmt
set ret
} $use_stmt
}
finish_test