diff options
author | unknown <heikki@hundin.mysql.fi> | 2002-04-18 10:40:32 +0300 |
---|---|---|
committer | unknown <heikki@hundin.mysql.fi> | 2002-04-18 10:40:32 +0300 |
commit | 209a500df9f97dcbc910a4c66f82efe2b10867c5 (patch) | |
tree | 4e69f6dc9cf132e85fb626a6120e6576baf6c127 /innobase | |
parent | 9c2c5a3636eb730fedbbeb613ee946ef15735be0 (diff) | |
download | mariadb-git-209a500df9f97dcbc910a4c66f82efe2b10867c5.tar.gz |
Many files:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/btr/btr0cur.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/dict/dict0crea.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/dict/dict0dict.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/dict/dict0load.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/dict/dict0mem.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/btr0btr.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/dict0mem.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/ibuf0ibuf.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/os0file.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/os0sync.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/row0mysql.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/row0upd.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/sync0sync.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/trx0sys.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/trx0trx.h:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/ibuf0ibuf.ic:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/os0sync.ic:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/sync0sync.ic:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/include/trx0sys.ic:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/lock/lock0lock.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/pars/pars0opt.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/que/que0que.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/row/row0ins.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/row/row0mysql.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/row/row0sel.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/row/row0upd.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/srv/srv0srv.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/sync/sync0sync.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/trx/trx0sys.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
innobase/trx/trx0trx.c:
Implement ON DELETE CASCADE and facilitate switching off of UNIQUE constraints and foreign keys
Diffstat (limited to 'innobase')
30 files changed, 768 insertions, 178 deletions
diff --git a/innobase/btr/btr0cur.c b/innobase/btr/btr0cur.c index ddcf5e8faf2..daebb8dbbaf 100644 --- a/innobase/btr/btr0cur.c +++ b/innobase/btr/btr0cur.c @@ -228,6 +228,7 @@ btr_cur_search_to_nth_level( ulint insert_planned; ulint buf_mode; ulint estimate; + ulint ignore_sec_unique; ulint root_height; #ifdef BTR_CUR_ADAPT btr_search_t* info; @@ -246,7 +247,9 @@ btr_cur_search_to_nth_level( #endif insert_planned = latch_mode & BTR_INSERT; estimate = latch_mode & BTR_ESTIMATE; - latch_mode = latch_mode & ~(BTR_INSERT | BTR_ESTIMATE); + ignore_sec_unique = latch_mode & BTR_IGNORE_SEC_UNIQUE; + latch_mode = latch_mode & ~(BTR_INSERT | BTR_ESTIMATE + | BTR_IGNORE_SEC_UNIQUE); ut_ad(!insert_planned || (mode == PAGE_CUR_LE)); @@ -343,7 +346,8 @@ btr_cur_search_to_nth_level( rw_latch = latch_mode; - if (insert_planned && ibuf_should_try(index)) { + if (insert_planned && ibuf_should_try(index, + ignore_sec_unique)) { /* Try insert to the insert buffer if the page is not in the buffer pool */ @@ -356,7 +360,6 @@ retry_page_get: buf_mode, IB__FILE__, __LINE__, mtr); - if (page == NULL) { /* This must be a search to perform an insert; try insert to the insert buffer */ @@ -365,7 +368,7 @@ retry_page_get: ut_ad(insert_planned); ut_ad(cursor->thr); - if (ibuf_should_try(index) && + if (ibuf_should_try(index, ignore_sec_unique) && ibuf_insert(tuple, index, space, page_no, cursor->thr)) { /* Insertion to the insert buffer succeeded */ diff --git a/innobase/dict/dict0crea.c b/innobase/dict/dict0crea.c index f15d36251e4..38c09c1011d 100644 --- a/innobase/dict/dict0crea.c +++ b/innobase/dict/dict0crea.c @@ -1201,7 +1201,8 @@ loop: ut_dulint_get_low(id), table->name, foreign->referenced_table_name, - foreign->n_fields); + foreign->n_fields + + (foreign->type << 24)); for (i = 0; i < foreign->n_fields; i++) { diff --git a/innobase/dict/dict0dict.c b/innobase/dict/dict0dict.c index d52483074cd..a6f268c2153 100644 --- a/innobase/dict/dict0dict.c +++ b/innobase/dict/dict0dict.c @@ -1648,7 +1648,7 @@ dict_foreign_find_index( ->col->name; if (ut_strlen(columns[i]) != ut_strlen(col_name) - || 0 != ut_memcmp(columns[i], + || 0 != ut_cmp_in_lower_case(columns[i], col_name, ut_strlen(col_name))) { break; @@ -1853,8 +1853,9 @@ dict_scan_col( ibool* success,/* out: TRUE if success */ dict_table_t* table, /* in: table in which the column is */ dict_col_t** column, /* out: pointer to column if success */ - char** column_name)/* out: pointer to column->name if + char** column_name,/* out: pointer to column->name if success */ + ulint* column_name_len)/* out: column name length */ { dict_col_t* col; char* old_ptr; @@ -1882,20 +1883,28 @@ dict_scan_col( ptr++; } - for (i = 0; i < dict_table_get_n_cols(table); i++) { + *column_name_len = (ulint)(ptr - old_ptr); + + if (table == NULL) { + *success = TRUE; + *column = NULL; + *column_name = old_ptr; + } else { + for (i = 0; i < dict_table_get_n_cols(table); i++) { - col = dict_table_get_nth_col(table, i); + col = dict_table_get_nth_col(table, i); - if (ut_strlen(col->name) == (ulint)(ptr - old_ptr) - && 0 == ut_cmp_in_lower_case(col->name, old_ptr, + if (ut_strlen(col->name) == (ulint)(ptr - old_ptr) + && 0 == ut_cmp_in_lower_case(col->name, old_ptr, (ulint)(ptr - old_ptr))) { - /* Found */ + /* Found */ - *success = TRUE; - *column = col; - *column_name = col->name; + *success = TRUE; + *column = col; + *column_name = col->name; - break; + break; + } } } @@ -1914,14 +1923,18 @@ dict_scan_table_name( /*=================*/ /* out: scanned to */ char* ptr, /* in: scanned to */ - dict_table_t** table, /* out: table object or NULL if error */ - char* name) /* in: foreign key table name */ + dict_table_t** table, /* out: table object or NULL */ + char* name, /* in: foreign key table name */ + ibool* success,/* out: TRUE if ok name found */ + char* second_table_name)/* in/out: buffer where to store + the referenced table name; must be at least + 2500 bytes */ { char* dot_ptr = NULL; char* old_ptr; ulint i; - char second_table_name[10000]; + *success = FALSE; *table = NULL; while (isspace(*ptr)) { @@ -1947,7 +1960,7 @@ dict_scan_table_name( ptr++; } - if (ptr - old_ptr > 9000) { + if (ptr - old_ptr > 2000) { return(old_ptr); } @@ -1978,6 +1991,8 @@ dict_scan_table_name( second_table_name[ptr - old_ptr] = '\0'; } + *success = TRUE; + *table = dict_table_get_low(second_table_name); if (*ptr == '`') { @@ -2043,9 +2058,12 @@ dict_create_foreign_constraints( ibool success; ulint error; ulint i; - dict_col_t* columns[1000]; - char* column_names[1000]; - + ulint j; + dict_col_t* columns[500]; + char* column_names[500]; + ulint column_name_lens[500]; + char referenced_table_name[2500]; + ut_ad(mutex_own(&(dict_sys->mutex))); table = dict_table_get_low(name); @@ -2090,7 +2108,7 @@ loop: /* Scan the columns in the first list */ col_loop1: ptr = dict_scan_col(ptr, &success, table, columns + i, - column_names + i); + column_names + i, column_name_lens + i); if (!success) { return(DB_CANNOT_ADD_CONSTRAINT); } @@ -2141,9 +2159,13 @@ col_loop1: 1 + ut_strlen(columns[i]->name)); } - ptr = dict_scan_table_name(ptr, &referenced_table, name); + ptr = dict_scan_table_name(ptr, &referenced_table, name, + &success, referenced_table_name); - if (!referenced_table) { + /* Note that referenced_table can be NULL if the user has suppressed + checking of foreign key constraints! */ + + if (!success || (!referenced_table && trx->check_foreigns)) { dict_foreign_free(foreign); return(DB_CANNOT_ADD_CONSTRAINT); @@ -2161,7 +2183,7 @@ col_loop1: col_loop2: ptr = dict_scan_col(ptr, &success, referenced_table, columns + i, - column_names + i); + column_names + i, column_name_lens + i); i++; if (!success) { @@ -2183,43 +2205,104 @@ col_loop2: return(DB_CANNOT_ADD_CONSTRAINT); } + ptr = dict_accept(ptr, "ON", &success); + + if (!success) { + + goto try_find_index; + } + + ptr = dict_accept(ptr, "DELETE", &success); + + if (!success) { + + goto try_find_index; + } + + ptr = dict_accept(ptr, "CASCADE", &success); + + if (success) { + + foreign->type = DICT_FOREIGN_ON_DELETE_CASCADE; + + goto try_find_index; + } + + ptr = dict_accept(ptr, "SET", &success); + + if (!success) { + + goto try_find_index; + } + + ptr = dict_accept(ptr, "NULL", &success); + + if (success) { + for (j = 0; j < foreign->n_fields; j++) { + if ((dict_index_get_nth_type( + foreign->foreign_index, j)->prtype) + & DATA_NOT_NULL) { + + /* It is not sensible to define SET NULL + if the column is not allowed to be NULL! */ + + dict_foreign_free(foreign); + return(DB_CANNOT_ADD_CONSTRAINT); + } + } + + foreign->type = DICT_FOREIGN_ON_DELETE_SET_NULL; + + goto try_find_index; + } + +try_find_index: /* Try to find an index which contains the columns as the first fields and in the right order, and the types are the same as in foreign->foreign_index */ - index = dict_foreign_find_index(referenced_table, column_names, i, + if (referenced_table) { + index = dict_foreign_find_index(referenced_table, + column_names, i, foreign->foreign_index); - - if (!index) { - dict_foreign_free(foreign); - return(DB_CANNOT_ADD_CONSTRAINT); + if (!index) { + dict_foreign_free(foreign); + return(DB_CANNOT_ADD_CONSTRAINT); + } + } else { + ut_a(trx->check_foreigns == FALSE); + index = NULL; } foreign->referenced_index = index; foreign->referenced_table = referenced_table; foreign->referenced_table_name = mem_heap_alloc(foreign->heap, - 1 + ut_strlen(referenced_table->name)); + 1 + ut_strlen(referenced_table_name)); - ut_memcpy(foreign->referenced_table_name, referenced_table->name, - 1 + ut_strlen(referenced_table->name)); + ut_memcpy(foreign->referenced_table_name, referenced_table_name, + 1 + ut_strlen(referenced_table_name)); foreign->referenced_col_names = mem_heap_alloc(foreign->heap, i * sizeof(void*)); for (i = 0; i < foreign->n_fields; i++) { foreign->referenced_col_names[i] = mem_heap_alloc(foreign->heap, - 1 + ut_strlen(columns[i]->name)); - ut_memcpy( - foreign->referenced_col_names[i], columns[i]->name, - 1 + ut_strlen(columns[i]->name)); + 1 + column_name_lens[i]); + ut_memcpy(foreign->referenced_col_names[i], column_names[i], + column_name_lens[i]); + (foreign->referenced_col_names[i])[column_name_lens[i]] = '\0'; } /* We found an ok constraint definition: add to the lists */ UT_LIST_ADD_LAST(foreign_list, table->foreign_list, foreign); - UT_LIST_ADD_LAST(referenced_list, referenced_table->referenced_list, + + if (referenced_table) { + UT_LIST_ADD_LAST(referenced_list, + referenced_table->referenced_list, foreign); + } goto loop; } @@ -3034,6 +3117,14 @@ dict_print_info_on_foreign_keys_in_create_format( buf2 += sprintf(buf2, ")"); + if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE) { + buf2 += sprintf(buf2, " ON DELETE CASCADE"); + } + + if (foreign->type == DICT_FOREIGN_ON_DELETE_SET_NULL) { + buf2 += sprintf(buf2, " ON DELETE SET NULL"); + } + foreign = UT_LIST_GET_NEXT(foreign_list, foreign); } diff --git a/innobase/dict/dict0load.c b/innobase/dict/dict0load.c index 29c98db4a53..4917359c748 100644 --- a/innobase/dict/dict0load.c +++ b/innobase/dict/dict0load.c @@ -946,6 +946,11 @@ dict_load_foreign( foreign->n_fields = mach_read_from_4(rec_get_nth_field(rec, 5, &len)); ut_a(len == 4); + + /* We store the type to the bits 24-31 of n_fields */ + + foreign->type = foreign->n_fields >> 24; + foreign->n_fields = foreign->n_fields & 0xFFFFFF; foreign->id = mem_heap_alloc(foreign->heap, ut_strlen(id) + 1); diff --git a/innobase/dict/dict0mem.c b/innobase/dict/dict0mem.c index 1f9a44aca35..52f46062065 100644 --- a/innobase/dict/dict0mem.c +++ b/innobase/dict/dict0mem.c @@ -61,7 +61,8 @@ dict_mem_table_create( table->mem_fix = 0; table->n_mysql_handles_opened = 0; - + table->n_foreign_key_checks_running = 0; + table->cached = FALSE; table->cols = mem_heap_alloc(heap, (n_cols + DATA_N_SYS_COLS) @@ -235,6 +236,7 @@ dict_mem_foreign_create(void) foreign->id = NULL; + foreign->type = 0; foreign->foreign_table_name = NULL; foreign->foreign_table = NULL; foreign->foreign_col_names = NULL; diff --git a/innobase/include/btr0btr.h b/innobase/include/btr0btr.h index bea85565125..d22f9d79c1c 100644 --- a/innobase/include/btr0btr.h +++ b/innobase/include/btr0btr.h @@ -49,6 +49,12 @@ inserted to the index, at the searched position */ /* This flag ORed to latch mode says that we do the search in query optimization */ #define BTR_ESTIMATE 1024 + +/* This flag ORed to latch mode says that we can ignore possible +UNIQUE definition on secondary indexes when we decide if we can use the +insert buffer to speed up inserts */ +#define BTR_IGNORE_SEC_UNIQUE 2048 + /****************************************************************** Gets a buffer page and declares its latching order level. */ UNIV_INLINE diff --git a/innobase/include/dict0mem.h b/innobase/include/dict0mem.h index 5ef0103087a..ef15c99fdba 100644 --- a/innobase/include/dict0mem.h +++ b/innobase/include/dict0mem.h @@ -249,6 +249,8 @@ struct dict_foreign_struct{ this memory heap */ char* id; /* id of the constraint as a null-terminated string */ + ulint type; /* 0 or DICT_FOREIGN_ON_DELETE_CASCADE + or DICT_FOREIGN_ON_DELETE_SET_NULL */ char* foreign_table_name;/* foreign table name */ dict_table_t* foreign_table; /* table where the foreign key is */ char** foreign_col_names;/* names of the columns in the @@ -278,6 +280,9 @@ struct dict_foreign_struct{ table */ }; +#define DICT_FOREIGN_ON_DELETE_CASCADE 1 +#define DICT_FOREIGN_ON_DELETE_SET_NULL 2 + #define DICT_INDEX_MAGIC_N 76789786 /* Data structure for a database table */ @@ -313,6 +318,12 @@ struct dict_table_struct{ NOT allowed until this count gets to zero; MySQL does NOT itself check the number of open handles at drop */ + ulint n_foreign_key_checks_running; + /* count of how many foreign key check + operations are currently being performed + on the table: we cannot drop the table while + there are foreign key checks running on + it! */ ibool cached; /* TRUE if the table object has been added to the dictionary cache */ lock_t* auto_inc_lock;/* a buffer for an auto-inc lock @@ -359,17 +370,16 @@ struct dict_table_struct{ after database startup or table creation */ ulint stat_modified_counter; /* when a row is inserted, updated, or deleted, - we add the row length to this number; we - calculate new estimates for the stat_... - values for the table and the indexes at an - interval of 2 GB or when about 1 / 16 of table - has been modified; also - when the estimate operation is called - for MySQL SHOW TABLE STATUS; the counter is - reset to zero at statistics calculation; - this counter - is not protected by any latch, because this - is only used for heuristics */ + we add 1 to this number; we calculate new + estimates for the stat_... values for the + table and the indexes at an interval of 2 GB + or when about 1 / 16 of table has been + modified; also when the estimate operation is + called for MySQL SHOW TABLE STATUS; the + counter is reset to zero at statistics + calculation; this counter is not protected by + any latch, because this is only used for + heuristics */ /*----------------------*/ mutex_t autoinc_mutex; /* mutex protecting the autoincrement diff --git a/innobase/include/ibuf0ibuf.h b/innobase/include/ibuf0ibuf.h index 99fb1595f49..fac28461be4 100644 --- a/innobase/include/ibuf0ibuf.h +++ b/innobase/include/ibuf0ibuf.h @@ -127,7 +127,11 @@ UNIV_INLINE ibool ibuf_should_try( /*============*/ - dict_index_t* index); /* in: index where to insert */ + dict_index_t* index, /* in: index where to insert */ + ulint ignore_sec_unique); /* in: if != 0, we should + ignore UNIQUE constraint on + a secondary index when we + decide */ /********************************************************************** Returns TRUE if the current OS thread is performing an insert buffer routine. */ diff --git a/innobase/include/ibuf0ibuf.ic b/innobase/include/ibuf0ibuf.ic index e969a0550da..0886c8c02cc 100644 --- a/innobase/include/ibuf0ibuf.ic +++ b/innobase/include/ibuf0ibuf.ic @@ -81,10 +81,16 @@ UNIV_INLINE ibool ibuf_should_try( /*============*/ - dict_index_t* index) /* in: index where to insert */ + dict_index_t* index, /* in: index where to insert */ + ulint ignore_sec_unique) /* in: if != 0, we should + ignore UNIQUE constraint on + a secondary index when we + decide */ { - if (!(index->type & (DICT_CLUSTERED | DICT_UNIQUE)) - && ibuf->meter > IBUF_THRESHOLD) { + if (!(index->type & DICT_CLUSTERED) + && (ignore_sec_unique || !(index->type & DICT_UNIQUE)) + && ibuf->meter > IBUF_THRESHOLD) { + ibuf_flush_count++; if (ibuf_flush_count % 8 == 0) { diff --git a/innobase/include/os0file.h b/innobase/include/os0file.h index 3dba8ba3158..01fa12955ff 100644 --- a/innobase/include/os0file.h +++ b/innobase/include/os0file.h @@ -341,6 +341,8 @@ os_aio_windows_handle( void** message2, ulint* type); /* out: OS_FILE_WRITE or ..._READ */ #endif + +/* Currently we do not use Posix async i/o */ #ifdef POSIX_ASYNC_IO /************************************************************************** This function is only used in Posix asynchronous i/o. Waits for an aio diff --git a/innobase/include/os0sync.h b/innobase/include/os0sync.h index 617f6b036fe..b2d613c4619 100644 --- a/innobase/include/os0sync.h +++ b/innobase/include/os0sync.h @@ -149,9 +149,9 @@ void os_mutex_free( /*==========*/ os_mutex_t mutex); /* in: mutex to free */ -#ifndef _WIN32 /************************************************************** -Acquires ownership of a fast mutex. */ +Acquires ownership of a fast mutex. Currently in Windows this is the same +as os_fast_mutex_lock! */ UNIV_INLINE ulint os_fast_mutex_trylock( @@ -160,7 +160,6 @@ os_fast_mutex_trylock( was reserved by another thread */ os_fast_mutex_t* fast_mutex); /* in: mutex to acquire */ -#endif /************************************************************** Releases ownership of a fast mutex. */ diff --git a/innobase/include/os0sync.ic b/innobase/include/os0sync.ic index aa00300dec5..6bff75d8ec6 100644 --- a/innobase/include/os0sync.ic +++ b/innobase/include/os0sync.ic @@ -10,9 +10,9 @@ Created 9/6/1995 Heikki Tuuri #include <winbase.h> #endif -#ifndef _WIN32 /************************************************************** -Acquires ownership of a fast mutex. */ +Acquires ownership of a fast mutex. Currently in Windows this is the same +as os_fast_mutex_lock! */ UNIV_INLINE ulint os_fast_mutex_trylock( @@ -23,20 +23,11 @@ os_fast_mutex_trylock( os_fast_mutex_t* fast_mutex) /* in: mutex to acquire */ { #ifdef __WIN__ - int ret; + EnterCriticalSection(fast_mutex); - /* TODO: TryEnterCriticalSection is probably not found from - NT versions < 4! */ - ret = TryEnterCriticalSection(fast_mutex); - - if (ret) { - return(0); - } - - return(1); + return(0); #else return((ulint) pthread_mutex_trylock(fast_mutex)); #endif } -#endif diff --git a/innobase/include/row0mysql.h b/innobase/include/row0mysql.h index 92693319175..13b3dffd874 100644 --- a/innobase/include/row0mysql.h +++ b/innobase/include/row0mysql.h @@ -209,6 +209,27 @@ row_update_for_mysql( row_prebuilt_t* prebuilt); /* in: prebuilt struct in MySQL handle */ /************************************************************************* +Creates an query graph node of 'update' type to be used in the MySQL +interface. */ + +upd_node_t* +row_create_update_node_for_mysql( +/*=============================*/ + /* out, own: update node */ + dict_table_t* table, /* in: table to update */ + mem_heap_t* heap); /* in: mem heap from which allocated */ +/************************************************************************** +Does a cascaded delete or set null in a foreign key operation. */ + +ulint +row_update_cascade_for_mysql( +/*=========================*/ + /* out: error code or DB_SUCCESS */ + que_thr_t* thr, /* in: query thread */ + upd_node_t* node, /* in: update node used in the cascade + or set null operation */ + dict_table_t* table); /* in: table where we do the operation */ +/************************************************************************* Does a table creation operation for MySQL. If the name of the created table ends to characters INNODB_MONITOR, then this also starts printing of monitor output by the master thread. */ diff --git a/innobase/include/row0upd.h b/innobase/include/row0upd.h index 106d3866b25..9a3e2463267 100644 --- a/innobase/include/row0upd.h +++ b/innobase/include/row0upd.h @@ -312,6 +312,11 @@ struct upd_node_struct{ ibool in_mysql_interface; /* TRUE if the update node was created for the MySQL interface */ + upd_node_t* cascade_node;/* NULL or an update node template which + is used to implement ON DELETE CASCADE + or ... SET NULL for foreign keys */ + mem_heap_t* cascade_heap;/* NULL or a mem heap where the cascade + node is created */ sel_node_t* select; /* query graph subtree implementing a base table cursor: the rows returned will be updated */ @@ -322,6 +327,11 @@ struct upd_node_struct{ of the MySQL interface */ dict_table_t* table; /* table where updated */ upd_t* update; /* update vector for the row */ + ulint update_n_fields; + /* when this struct is used to implement + a cascade operation for foreign keys, we store + here the size of the buffer allocated for use + as the update vector */ sym_node_list_t columns;/* symbol table nodes for the columns to retrieve from the table */ ibool has_clust_rec_x_lock; diff --git a/innobase/include/sync0sync.h b/innobase/include/sync0sync.h index 6c3bff66e27..4f55709a5d7 100644 --- a/innobase/include/sync0sync.h +++ b/innobase/include/sync0sync.h @@ -359,12 +359,17 @@ V Memory pool mutex */ /* Latching order levels */ + +/* User transaction locks are higher than any of the latch levels below: +no latches are allowed when a thread goes to wait for a normal table +or row lock! */ +#define SYNC_USER_TRX_LOCK 9999 #define SYNC_NO_ORDER_CHECK 3000 /* this can be used to suppress latching order checking */ #define SYNC_LEVEL_NONE 2000 /* default: level not defined */ +#define SYNC_FOREIGN_KEY_CHECK 1001 #define SYNC_DICT 1000 #define SYNC_DICT_AUTOINC_MUTEX 999 -#define SYNC_FOREIGN_KEY_CHECK 998 #define SYNC_PURGE_IS_RUNNING 997 #define SYNC_DICT_HEADER 995 #define SYNC_IBUF_HEADER 914 @@ -429,7 +434,7 @@ implementation of a mutual exclusion semaphore. */ struct mutex_struct { ulint lock_word; /* This ulint is the target of the atomic test-and-set instruction in Win32 */ -#ifndef _WIN32 +#if !defined(_WIN32) || !defined(UNIV_CAN_USE_X86_ASSEMBLER) os_fast_mutex_t os_fast_mutex; /* In other systems we use this OS mutex in place of lock_word */ diff --git a/innobase/include/sync0sync.ic b/innobase/include/sync0sync.ic index 9531377ce0b..9014eb5fb54 100644 --- a/innobase/include/sync0sync.ic +++ b/innobase/include/sync0sync.ic @@ -53,7 +53,7 @@ mutex_test_and_set( 1 */ mutex_t* mutex) /* in: mutex */ { -#ifdef _WIN32 +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) ulint res; ulint* lw; /* assembler code is used to ensure that lock_word is loaded from memory */ @@ -120,7 +120,7 @@ mutex_reset_lock_word( /*==================*/ mutex_t* mutex) /* in: mutex */ { -#ifdef _WIN32 +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) ulint* lw; /* assembler code is used to ensure that lock_word is loaded from memory */ ut_ad(mutex); diff --git a/innobase/include/trx0sys.h b/innobase/include/trx0sys.h index 98c726fb118..60d5adb72d1 100644 --- a/innobase/include/trx0sys.h +++ b/innobase/include/trx0sys.h @@ -259,7 +259,7 @@ therefore 256 */ /* The offset of the transaction system header on the page */ #define TRX_SYS FSEG_PAGE_DATA -/* Transaction system header; protected by trx_sys->mutex */ +/* Transaction system header */ /*-------------------------------------------------------------*/ #define TRX_SYS_TRX_ID_STORE 0 /* the maximum trx id or trx number modulo TRX_SYS_TRX_ID_UPDATE_MARGIN diff --git a/innobase/include/trx0sys.ic b/innobase/include/trx0sys.ic index 786e7905933..ada2d8cb19c 100644 --- a/innobase/include/trx0sys.ic +++ b/innobase/include/trx0sys.ic @@ -93,7 +93,6 @@ trx_sysf_get( { trx_sysf_t* header; - ut_ad(mutex_own(&(kernel_mutex))); ut_ad(mtr); header = TRX_SYS + buf_page_get(TRX_SYS_SPACE, TRX_SYS_PAGE_NO, diff --git a/innobase/include/trx0trx.h b/innobase/include/trx0trx.h index 26c9ace08b6..261f33d3dc3 100644 --- a/innobase/include/trx0trx.h +++ b/innobase/include/trx0trx.h @@ -298,6 +298,17 @@ struct trx_struct{ of view of concurrency control: TRX_ACTIVE, TRX_COMMITTED_IN_MEMORY, ... */ + ibool check_foreigns; /* normally TRUE, but if the user + wants to suppress foreign key checks, + (in table imports, for example) we + set this FALSE */ + ibool check_unique_secondary; + /* normally TRUE, but if the user + wants to speed up inserts by + suppressing unique key checks + for secondary indexes when we decide + if we can use the insert buffer for + them, we set this FALSE */ dulint id; /* transaction id */ dulint no; /* transaction serialization number == max trx id when the transaction is @@ -328,6 +339,9 @@ struct trx_struct{ /* how many tables the current SQL statement uses, except those in consistent read */ + ibool has_dict_foreign_key_check_lock; + /* TRUE if the trx currently holds + an s-lock on dict_foreign_... */ ibool has_search_latch; /* TRUE if this trx has latched the search system latch in S-mode */ diff --git a/innobase/lock/lock0lock.c b/innobase/lock/lock0lock.c index 0b2b85d0337..9fd6811d639 100644 --- a/innobase/lock/lock0lock.c +++ b/innobase/lock/lock0lock.c @@ -1534,6 +1534,8 @@ lock_rec_enqueue_waiting( if (que_thr_stop(thr)) { + ut_a(0); + return(DB_QUE_THR_SUSPENDED); } @@ -2918,6 +2920,7 @@ lock_table_enqueue_waiting( stopped anyway */ if (que_thr_stop(thr)) { + ut_a(0); return(DB_QUE_THR_SUSPENDED); } diff --git a/innobase/pars/pars0opt.c b/innobase/pars/pars0opt.c index 6f4957f96ee..9814c4b21cc 100644 --- a/innobase/pars/pars0opt.c +++ b/innobase/pars/pars0opt.c @@ -526,7 +526,8 @@ opt_search_plan_for_table( dict_index_t* best_index; ulint n_fields; ulint goodness; - ulint last_op; + ulint last_op = 75946965; /* Eliminate a Purify + warning */ ulint best_goodness; ulint best_last_op; ulint mix_id_pos; diff --git a/innobase/que/que0que.c b/innobase/que/que0que.c index 97843311d21..41a90a7fb42 100644 --- a/innobase/que/que0que.c +++ b/innobase/que/que0que.c @@ -555,6 +555,12 @@ que_graph_free_recursive( btr_pcur_free_for_mysql(upd->pcur); } + que_graph_free_recursive(upd->cascade_node); + + if (upd->cascade_heap) { + mem_heap_free(upd->cascade_heap); + } + que_graph_free_recursive(upd->select); mem_heap_free(upd->heap); @@ -1110,9 +1116,6 @@ que_thr_move_to_run_state_for_mysql( trx->n_active_thrs++; thr->is_active = TRUE; - - ut_ad((thr->graph)->n_active_thrs == 1); - ut_ad(trx->n_active_thrs == 1); } thr->state = QUE_THR_RUNNING; diff --git a/innobase/row/row0ins.c b/innobase/row/row0ins.c index 04df66c3bbd..0ba6db39e2a 100644 --- a/innobase/row/row0ins.c +++ b/innobase/row/row0ins.c @@ -356,6 +356,223 @@ row_ins_dupl_error_with_rec( } /************************************************************************* +Either deletes or sets the referencing columns SQL NULL in a child row. +Used in ON DELETE ... clause for foreign keys when a parent row is +deleted. */ +static +ulint +row_ins_foreign_delete_or_set_null( +/*===============================*/ + /* out: DB_SUCCESS, DB_LOCK_WAIT, + or error code */ + que_thr_t* thr, /* in: query thread whose run_node + is an update node */ + dict_foreign_t* foreign, /* in: foreign key constraint whose + type is != 0 */ + btr_pcur_t* pcur, /* in: cursor placed on a matching + index record in the child table */ + mtr_t* mtr) /* in: mtr holding the latch of pcur + page */ +{ + upd_node_t* node; + upd_node_t* cascade; + dict_table_t* table = foreign->foreign_table; + dict_index_t* index; + dict_index_t* clust_index; + dtuple_t* ref; + mem_heap_t* tmp_heap; + rec_t* rec; + rec_t* clust_rec; + upd_t* update; + ulint err; + ulint i; + char err_buf[1000]; + + ut_a(thr && foreign && pcur && mtr); + + node = thr->run_node; + + if (node->cascade_node == NULL) { + /* Extend our query graph by creating a child to current + update node. The child is used in the cascade or set null + operation. */ + + node->cascade_heap = mem_heap_create(128); + node->cascade_node = row_create_update_node_for_mysql( + table, node->cascade_heap); + que_node_set_parent(node->cascade_node, node); + } + + /* Initialize cascade_node to do the operation we want. Note that we + use the SAME cascade node to do all foreign key operations of the + SQL DELETE: the table of the cascade node may change if there are + several child tables to the table where the delete is done! */ + + cascade = node->cascade_node; + + cascade->table = table; + + if (foreign->type == DICT_FOREIGN_ON_DELETE_CASCADE ) { + cascade->is_delete = TRUE; + } else { + cascade->is_delete = FALSE; + + if (foreign->n_fields > cascade->update_n_fields) { + /* We have to make the update vector longer */ + + cascade->update = upd_create(foreign->n_fields, + node->cascade_heap); + cascade->update_n_fields = foreign->n_fields; + } + } + + index = btr_pcur_get_btr_cur(pcur)->index; + + rec = btr_pcur_get_rec(pcur); + + if (index->type & DICT_CLUSTERED) { + /* pcur is already positioned in the clustered index of + the child table */ + + clust_index = index; + clust_rec = rec; + } else { + /* We have to look for the record in the clustered index + in the child table */ + + clust_index = dict_table_get_first_index(table); + + tmp_heap = mem_heap_create(256); + + ref = row_build_row_ref(ROW_COPY_POINTERS, index, rec, + tmp_heap); + btr_pcur_open_with_no_init(clust_index, ref, + PAGE_CUR_LE, BTR_SEARCH_LEAF, + cascade->pcur, 0, mtr); + + mem_heap_free(tmp_heap); + + clust_rec = btr_pcur_get_rec(cascade->pcur); + } + + if (!page_rec_is_user_rec(clust_rec)) { + fprintf(stderr, "InnoDB: error in cascade of a foreign key op\n" + "InnoDB: index %s table %s\n", index->name, + index->table->name); + + rec_sprintf(err_buf, 900, rec); + fprintf(stderr, "InnoDB: record %s\n", err_buf); + + rec_sprintf(err_buf, 900, clust_rec); + fprintf(stderr, "InnoDB: clustered record %s\n", err_buf); + + fprintf(stderr, + "InnoDB: Make a detailed bug report and send it\n"); + fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n"); + + err = DB_SUCCESS; + + goto nonstandard_exit_func; + } + + /* Set an X-lock on the row to delete or update in the child table */ + + err = lock_table(0, table, LOCK_IX, thr); + + if (err == DB_SUCCESS) { + err = lock_clust_rec_read_check_and_lock(0, clust_rec, + clust_index, LOCK_X, thr); + } + + if (err != DB_SUCCESS) { + + goto nonstandard_exit_func; + } + + if (rec_get_deleted_flag(clust_rec)) { + /* This should never happen since we already have an S-lock + on non-delete-marked clust_rec or secondary index record! */ + + fprintf(stderr, + "InnoDB: error 2 in cascade of a foreign key op\n" + "InnoDB: index %s table %s\n", index->name, + index->table->name); + + rec_sprintf(err_buf, 900, rec); + fprintf(stderr, "InnoDB: record %s\n", err_buf); + + rec_sprintf(err_buf, 900, clust_rec); + fprintf(stderr, "InnoDB: clustered record %s\n", err_buf); + + fprintf(stderr, + "InnoDB: Make a detailed bug report and send it\n"); + fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n"); + + ut_a(0); + + goto nonstandard_exit_func; + } + + if (foreign->type == DICT_FOREIGN_ON_DELETE_SET_NULL) { + /* Build the appropriate update vector which sets + foreign->n_fields first fields in rec to SQL NULL */ + + update = cascade->update; + + update->info_bits = 0; + update->n_fields = foreign->n_fields; + + for (i = 0; i < foreign->n_fields; i++) { + (update->fields + i)->field_no + = dict_table_get_nth_col_pos(table, + dict_index_get_nth_col_no(index, i)); + (update->fields + i)->exp = NULL; + (update->fields + i)->new_val.len = UNIV_SQL_NULL; + (update->fields + i)->new_val.data = NULL; + (update->fields + i)->extern_storage = FALSE; + } + } + + /* Store pcur position and initialize or store the cascade node + pcur stored position */ + + btr_pcur_store_position(pcur, mtr); + + if (index == clust_index) { + btr_pcur_copy_stored_position(cascade->pcur, pcur); + } else { + btr_pcur_store_position(cascade->pcur, mtr); + } + + mtr_commit(mtr); + + ut_a(cascade->pcur->rel_pos == BTR_PCUR_ON); + + cascade->state = UPD_NODE_UPDATE_CLUSTERED; + + err = row_update_cascade_for_mysql(thr, cascade, + foreign->foreign_table); + mtr_start(mtr); + + /* Restore pcur position */ + + btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr); + + return(err); + +nonstandard_exit_func: + + btr_pcur_store_position(pcur, mtr); + + mtr_commit(mtr); + mtr_start(mtr); + + btr_pcur_restore_position(BTR_SEARCH_LEAF, pcur, mtr); + + return(err); +} + +/************************************************************************* Sets a shared lock on a record. Used in locking possible duplicate key records. */ static @@ -416,6 +633,13 @@ row_ins_check_foreign_constraint( ut_ad(rw_lock_own(&dict_foreign_key_check_lock, RW_LOCK_SHARED)); + if (thr_get_trx(thr)->check_foreigns == FALSE) { + /* The user has suppressed foreign key checks currently for + this session */ + + return(DB_SUCCESS); + } + /* If any of the foreign key fields in entry is SQL NULL, we suppress the foreign key check: this is compatible with Oracle, for example */ @@ -478,8 +702,8 @@ row_ins_check_foreign_constraint( goto next_rec; } - - /* Try to place a lock on the index record */ + + /* Try to place a lock on the index record */ err = row_ins_set_shared_rec_lock(rec, check_index, thr); @@ -501,11 +725,21 @@ row_ins_check_foreign_constraint( if (check_ref) { err = DB_SUCCESS; + + break; + } else if (foreign->type != 0) { + err = + row_ins_foreign_delete_or_set_null( + thr, foreign, &pcur, &mtr); + + if (err != DB_SUCCESS) { + + break; + } } else { err = DB_ROW_IS_REFERENCED; + break; } - - break; } } @@ -534,6 +768,8 @@ next_rec: } } + btr_pcur_close(&pcur); + mtr_commit(&mtr); /* Restore old value */ @@ -561,6 +797,10 @@ row_ins_check_foreign_constraints( { dict_foreign_t* foreign; ulint err; + trx_t* trx; + ibool got_s_lock = FALSE; + + trx = thr_get_trx(thr); foreign = UT_LIST_GET_FIRST(table->foreign_list); @@ -569,16 +809,26 @@ row_ins_check_foreign_constraints( if (foreign->referenced_table == NULL) { dict_table_get(foreign->referenced_table_name, - thr_get_trx(thr)); + trx); } - rw_lock_s_lock(&dict_foreign_key_check_lock); + if (!trx->has_dict_foreign_key_check_lock) { + got_s_lock = TRUE; + + rw_lock_s_lock(&dict_foreign_key_check_lock); + + trx->has_dict_foreign_key_check_lock = TRUE; + } err = row_ins_check_foreign_constraint(TRUE, foreign, table, index, entry, thr); + if (got_s_lock) { - rw_lock_s_unlock(&dict_foreign_key_check_lock); + rw_lock_s_unlock(&dict_foreign_key_check_lock); + trx->has_dict_foreign_key_check_lock = FALSE; + } + if (err != DB_SUCCESS) { return(err); } @@ -868,13 +1118,14 @@ row_ins_index_entry_low( ulint n_ext_vec,/* in: number of fields in ext_vec */ que_thr_t* thr) /* in: query thread */ { - btr_cur_t cursor; + btr_cur_t cursor; + ulint ignore_sec_unique = 0; ulint modify; rec_t* insert_rec; rec_t* rec; ulint err; ulint n_unique; - big_rec_t* big_rec = NULL; + big_rec_t* big_rec = NULL; mtr_t mtr; log_free_check(); @@ -887,8 +1138,13 @@ row_ins_index_entry_low( the function will return in both low_match and up_match of the cursor sensible values */ + if (!(thr_get_trx(thr)->check_unique_secondary)) { + ignore_sec_unique = BTR_IGNORE_SEC_UNIQUE; + } + btr_cur_search_to_nth_level(index, 0, entry, PAGE_CUR_LE, - mode | BTR_INSERT, &cursor, 0, &mtr); + mode | BTR_INSERT | ignore_sec_unique, + &cursor, 0, &mtr); if (cursor.flag == BTR_CUR_INSERT_TO_IBUF) { /* The insertion was made to the insert buffer already during diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c index 2aca698eebd..7969394f5e4 100644 --- a/innobase/row/row0mysql.c +++ b/innobase/row/row0mysql.c @@ -499,29 +499,24 @@ UNIV_INLINE void row_update_statistics_if_needed( /*============================*/ - row_prebuilt_t* prebuilt) /* in: prebuilt struct */ + dict_table_t* table) /* in: table */ { ulint counter; - counter = prebuilt->table->stat_modified_counter; - - /* Since the physical size of an InnoDB row is bigger than the - MySQL row len, we put a safety factor 2 below */ - - counter += 2 * prebuilt->mysql_row_len; + counter = table->stat_modified_counter; - prebuilt->table->stat_modified_counter = counter; + table->stat_modified_counter = counter + 1; /* Calculate new statistics if 1 / 16 of table has been modified since the last time a statistics batch was run, or if - stat_modified_counter > 2 000 000 000 (to avoid wrap-around) */ + stat_modified_counter > 2 000 000 000 (to avoid wrap-around). + We calculate statistics at most every 16th round, since we may have + a counter table which is very small and updated very often. */ if (counter > 2000000000 - || ((ib_longlong)counter > - (UNIV_PAGE_SIZE * prebuilt->table->stat_clustered_index_size) - / 16)) { + || ((ib_longlong)counter > 16 + table->stat_n_rows / 16)) { - dict_update_statistics(prebuilt->table); + dict_update_statistics(table); } } @@ -712,7 +707,7 @@ run_again: prebuilt->table->stat_n_rows--; } - row_update_statistics_if_needed(prebuilt); + row_update_statistics_if_needed(prebuilt->table); trx->op_info = ""; return((int) err); @@ -746,6 +741,43 @@ row_prebuild_sel_graph( } /************************************************************************* +Creates an query graph node of 'update' type to be used in the MySQL +interface. */ + +upd_node_t* +row_create_update_node_for_mysql( +/*=============================*/ + /* out, own: update node */ + dict_table_t* table, /* in: table to update */ + mem_heap_t* heap) /* in: mem heap from which allocated */ +{ + upd_node_t* node; + + node = upd_node_create(heap); + + node->in_mysql_interface = TRUE; + node->is_delete = FALSE; + node->searched_update = FALSE; + node->select_will_do_update = FALSE; + node->select = NULL; + node->pcur = btr_pcur_create_for_mysql(); + node->table = table; + + node->update = upd_create(dict_table_get_n_cols(table), heap); + + node->update_n_fields = dict_table_get_n_cols(table); + + UT_LIST_INIT(node->columns); + node->has_clust_rec_x_lock = TRUE; + node->cmpl_info = 0; + + node->table_sym = NULL; + node->col_assign_list = NULL; + + return(node); +} + +/************************************************************************* Gets pointer to a prebuilt update vector used in updates. If the update graph has not yet been built in the prebuilt struct, then this function first builds it. */ @@ -767,26 +799,9 @@ row_get_prebuilt_update_vector( /* Not called before for this handle: create an update node and query graph to the prebuilt struct */ - node = upd_node_create(prebuilt->heap); - - prebuilt->upd_node = node; + node = row_create_update_node_for_mysql(table, prebuilt->heap); - node->in_mysql_interface = TRUE; - node->is_delete = FALSE; - node->searched_update = FALSE; - node->select_will_do_update = FALSE; - node->select = NULL; - node->pcur = btr_pcur_create_for_mysql(); - node->table = table; - - node->update = upd_create(dict_table_get_n_cols(table), - prebuilt->heap); - UT_LIST_INIT(node->columns); - node->has_clust_rec_x_lock = TRUE; - node->cmpl_info = 0; - - node->table_sym = NULL; - node->col_assign_list = NULL; + prebuilt->upd_node = node; prebuilt->upd_graph = que_node_get_parent( @@ -914,7 +929,7 @@ run_again: que_thr_stop_for_mysql_no_error(thr, trx); - if (prebuilt->upd_node->is_delete) { + if (node->is_delete) { if (prebuilt->table->stat_n_rows > 0) { prebuilt->table->stat_n_rows--; } @@ -924,13 +939,66 @@ run_again: srv_n_rows_updated++; } - row_update_statistics_if_needed(prebuilt); + row_update_statistics_if_needed(prebuilt->table); trx->op_info = ""; return((int) err); } +/************************************************************************** +Does a cascaded delete or set null in a foreign key operation. */ + +ulint +row_update_cascade_for_mysql( +/*=========================*/ + /* out: error code or DB_SUCCESS */ + que_thr_t* thr, /* in: query thread */ + upd_node_t* node, /* in: update node used in the cascade + or set null operation */ + dict_table_t* table) /* in: table where we do the operation */ +{ + ulint err; + trx_t* trx; + + trx = thr_get_trx(thr); + +run_again: + thr->run_node = node; + thr->prev_node = node; + + row_upd_step(thr); + + err = trx->error_state; + + if (err == DB_LOCK_WAIT) { + que_thr_stop_for_mysql(thr); + + row_mysql_handle_errors(&err, trx, thr, NULL); + + goto run_again; + } + + if (err != DB_SUCCESS) { + + return(err); + } + + if (node->is_delete) { + if (table->stat_n_rows > 0) { + table->stat_n_rows--; + } + + srv_n_rows_deleted++; + } else { + srv_n_rows_updated++; + } + + row_update_statistics_if_needed(table); + + return(err); +} + /************************************************************************* Checks if a table is such that we automatically created a clustered index on it (on row id). */ @@ -1169,6 +1237,7 @@ row_create_table_for_mysql( /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ + rw_lock_x_lock(&(dict_foreign_key_check_lock)); mutex_enter(&(dict_sys->mutex)); heap = mem_heap_create(512); @@ -1221,6 +1290,8 @@ row_create_table_for_mysql( } mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); + que_graph_free((que_t*) que_node_get_parent(thr)); trx->op_info = ""; @@ -1268,6 +1339,7 @@ row_create_index_for_mysql( /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ + rw_lock_x_lock(&(dict_foreign_key_check_lock)); mutex_enter(&(dict_sys->mutex)); heap = mem_heap_create(512); @@ -1298,6 +1370,7 @@ row_create_index_for_mysql( } mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); que_graph_free((que_t*) que_node_get_parent(thr)); @@ -1353,6 +1426,7 @@ row_table_add_foreign_constraints( /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ + rw_lock_x_lock(&(dict_foreign_key_check_lock)); mutex_enter(&(dict_sys->mutex)); trx->dict_operation = TRUE; @@ -1377,6 +1451,7 @@ row_table_add_foreign_constraints( } mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); return((int) err); } @@ -1471,7 +1546,8 @@ loop: goto already_dropped; } - if (table->n_mysql_handles_opened > 0) { + if (table->n_mysql_handles_opened > 0 + || table->n_foreign_key_checks_running > 0) { return(n_tables + n_tables_dropped); } @@ -1717,6 +1793,9 @@ row_drop_table_for_mysql( no deadlocks can occur then in these operations */ if (!has_dict_mutex) { + /* Prevent foreign key checks while we are dropping the table */ + rw_lock_x_lock(&(dict_foreign_key_check_lock)); + mutex_enter(&(dict_sys->mutex)); } @@ -1729,9 +1808,6 @@ row_drop_table_for_mysql( graph->fork_type = QUE_FORK_MYSQL_INTERFACE; - /* Prevent foreign key checks while we are dropping the table */ - rw_lock_x_lock(&(dict_foreign_key_check_lock)); - /* Prevent purge from running while we are dropping the table */ rw_lock_s_lock(&(purge_sys->purge_is_running)); @@ -1766,6 +1842,22 @@ row_drop_table_for_mysql( goto funct_exit; } + if (table->n_foreign_key_checks_running > 0) { + + ut_print_timestamp(stderr); + fprintf(stderr, + " InnoDB: You are trying to drop table %s\n" + "InnoDB: though there are foreign key check running on it.\n" + "InnoDB: Adding the table to the background drop queue.\n", + table->name); + + row_add_table_to_background_drop_list(table); + + err = DB_SUCCESS; + + goto funct_exit; + } + /* Remove any locks there are on the table or its records */ lock_reset_all_on_table(table); @@ -1793,10 +1885,9 @@ row_drop_table_for_mysql( funct_exit: rw_lock_s_unlock(&(purge_sys->purge_is_running)); - rw_lock_x_unlock(&(dict_foreign_key_check_lock)); - if (!has_dict_mutex) { mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); } que_graph_free(graph); @@ -1832,6 +1923,7 @@ row_drop_database_for_mysql( trx_start_if_not_started(trx); loop: + rw_lock_x_lock(&(dict_foreign_key_check_lock)); mutex_enter(&(dict_sys->mutex)); while (table_name = dict_get_first_table_name_in_db(name)) { @@ -1873,6 +1965,7 @@ loop: } mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); trx_commit_for_mysql(trx); @@ -2009,6 +2102,7 @@ row_rename_table_for_mysql( /* Serialize data dictionary operations with dictionary mutex: no deadlocks can occur then in these operations */ + rw_lock_x_lock(&(dict_foreign_key_check_lock)); mutex_enter(&(dict_sys->mutex)); table = dict_table_get_low(old_name); @@ -2090,6 +2184,7 @@ row_rename_table_for_mysql( } funct_exit: mutex_exit(&(dict_sys->mutex)); + rw_lock_x_unlock(&(dict_foreign_key_check_lock)); que_graph_free(graph); diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c index 9ebd47c25bf..a516699edf9 100644 --- a/innobase/row/row0sel.c +++ b/innobase/row/row0sel.c @@ -2115,8 +2115,14 @@ row_sel_store_mysql_rec( extern_field_heap = NULL; } } else { - mysql_rec[templ->mysql_null_byte_offset] |= + if (!templ->mysql_null_bit_mask) { + fprintf(stderr, +"InnoDB: Error: trying to return an SQL NULL field in a non-null\n" +"innoDB: column! Table name %s\n", prebuilt->table->name); + } else { + mysql_rec[templ->mysql_null_byte_offset] |= (byte) (templ->mysql_null_bit_mask); + } } } } diff --git a/innobase/row/row0upd.c b/innobase/row/row0upd.c index a566e29f2c3..457cb72aaad 100644 --- a/innobase/row/row0upd.c +++ b/innobase/row/row0upd.c @@ -73,8 +73,7 @@ steps of query graph execution. */ /************************************************************************* Checks if index currently is mentioned as a referenced index in a foreign -key constraint. This function also loads into the dictionary cache the -possible referencing table. */ +key constraint. */ static ibool row_upd_index_is_referenced( @@ -85,44 +84,28 @@ row_upd_index_is_referenced( the referencing table has been dropped when we leave this function: this function is only for heuristic use! */ - dict_index_t* index) /* in: index */ + dict_index_t* index, /* in: index */ + trx_t* trx) /* in: transaction */ { - dict_table_t* table = index->table; + dict_table_t* table = index->table; dict_foreign_t* foreign; - ulint phase = 1; -try_again: if (!UT_LIST_GET_FIRST(table->referenced_list)) { return(FALSE); } - if (phase == 2) { - mutex_enter(&(dict_sys->mutex)); + if (!trx->has_dict_foreign_key_check_lock) { + rw_lock_s_lock(&dict_foreign_key_check_lock); } - rw_lock_s_lock(&dict_foreign_key_check_lock); - foreign = UT_LIST_GET_FIRST(table->referenced_list); while (foreign) { if (foreign->referenced_index == index) { - if (foreign->foreign_table == NULL) { - if (phase == 2) { - dict_table_get_low(foreign-> - foreign_table_name); - } else { - phase = 2; - rw_lock_s_unlock( - &dict_foreign_key_check_lock); - goto try_again; - } - } - - rw_lock_s_unlock(&dict_foreign_key_check_lock); - if (phase == 2) { - mutex_exit(&(dict_sys->mutex)); + if (!trx->has_dict_foreign_key_check_lock) { + rw_lock_s_unlock(&dict_foreign_key_check_lock); } return(TRUE); @@ -131,10 +114,8 @@ try_again: foreign = UT_LIST_GET_NEXT(referenced_list, foreign); } - rw_lock_s_unlock(&dict_foreign_key_check_lock); - - if (phase == 2) { - mutex_exit(&(dict_sys->mutex)); + if (!trx->has_dict_foreign_key_check_lock) { + rw_lock_s_unlock(&dict_foreign_key_check_lock); } return(FALSE); @@ -160,8 +141,17 @@ row_upd_check_references_constraints( dict_foreign_t* foreign; mem_heap_t* heap; dtuple_t* entry; + trx_t* trx; rec_t* rec; ulint err; + ibool got_s_lock = FALSE; + + if (UT_LIST_GET_FIRST(table->referenced_list) == NULL) { + + return(DB_SUCCESS); + } + + trx = thr_get_trx(thr); rec = btr_pcur_get_rec(pcur); @@ -173,17 +163,61 @@ row_upd_check_references_constraints( mtr_start(mtr); - rw_lock_s_lock(&dict_foreign_key_check_lock); + if (!trx->has_dict_foreign_key_check_lock) { + got_s_lock = TRUE; + rw_lock_s_lock(&dict_foreign_key_check_lock); + + trx->has_dict_foreign_key_check_lock = TRUE; + } + foreign = UT_LIST_GET_FIRST(table->referenced_list); while (foreign) { if (foreign->referenced_index == index) { + if (foreign->foreign_table == NULL) { + dict_table_get(foreign->foreign_table_name, + trx); + } + if (foreign->foreign_table) { + mutex_enter(&(dict_sys->mutex)); + + (foreign->foreign_table + ->n_foreign_key_checks_running)++; + + mutex_exit(&(dict_sys->mutex)); + } + + /* NOTE that if the thread ends up waiting for a lock + we will release dict_foreign_key_check_lock + temporarily! But the counter on the table + protects 'foreign' from being dropped while the check + is running. */ + err = row_ins_check_foreign_constraint(FALSE, foreign, table, index, entry, thr); + + if (foreign->foreign_table) { + mutex_enter(&(dict_sys->mutex)); + + ut_a(foreign->foreign_table + ->n_foreign_key_checks_running > 0); + + (foreign->foreign_table + ->n_foreign_key_checks_running)--; + + mutex_exit(&(dict_sys->mutex)); + } + if (err != DB_SUCCESS) { - rw_lock_s_unlock(&dict_foreign_key_check_lock); + if (got_s_lock) { + rw_lock_s_unlock( + &dict_foreign_key_check_lock); + trx->has_dict_foreign_key_check_lock + = FALSE; + } + mem_heap_free(heap); return(err); @@ -193,7 +227,11 @@ row_upd_check_references_constraints( foreign = UT_LIST_GET_NEXT(referenced_list, foreign); } - rw_lock_s_unlock(&dict_foreign_key_check_lock); + if (got_s_lock) { + rw_lock_s_unlock(&dict_foreign_key_check_lock); + trx->has_dict_foreign_key_check_lock = FALSE; + } + mem_heap_free(heap); return(DB_SUCCESS); @@ -222,6 +260,9 @@ upd_node_create( node->index = NULL; node->update = NULL; + node->cascade_heap = NULL; + node->cascade_node = NULL; + node->select = NULL; node->heap = mem_heap_create(128); @@ -1027,7 +1068,7 @@ row_upd_sec_index_entry( index = node->index; - check_ref = row_upd_index_is_referenced(index); + check_ref = row_upd_index_is_referenced(index, thr_get_trx(thr)); heap = mem_heap_create(1024); @@ -1391,7 +1432,7 @@ row_upd_clust_step( index = dict_table_get_first_index(node->table); - check_ref = row_upd_index_is_referenced(index); + check_ref = row_upd_index_is_referenced(index, thr_get_trx(thr)); pcur = node->pcur; diff --git a/innobase/srv/srv0srv.c b/innobase/srv/srv0srv.c index 8afe1396f1b..d4aa085479b 100644 --- a/innobase/srv/srv0srv.c +++ b/innobase/srv/srv0srv.c @@ -2017,15 +2017,18 @@ srv_suspend_mysql_thread( /*=====================*/ /* out: TRUE if the lock wait timeout was exceeded */ - que_thr_t* thr) /* in: query thread associated with - the MySQL OS thread */ + que_thr_t* thr) /* in: query thread associated with the MySQL + OS thread */ { srv_slot_t* slot; os_event_t event; double wait_time; - + trx_t* trx; + ut_ad(!mutex_own(&kernel_mutex)); + trx = thr_get_trx(thr); + os_event_set(srv_lock_timeout_thread_event); mutex_enter(&kernel_mutex); @@ -2061,10 +2064,21 @@ srv_suspend_mysql_thread( srv_conc_force_exit_innodb(thr_get_trx(thr)); + /* Release possible foreign key check latch */ + if (trx->has_dict_foreign_key_check_lock) { + + rw_lock_s_unlock(&dict_foreign_key_check_lock); + } + /* Wait for the release */ os_event_wait(event); + if (trx->has_dict_foreign_key_check_lock) { + + rw_lock_s_lock(&dict_foreign_key_check_lock); + } + /* Return back inside InnoDB */ srv_conc_force_enter_innodb(thr_get_trx(thr)); diff --git a/innobase/sync/sync0sync.c b/innobase/sync/sync0sync.c index 8b2a39e15eb..f5b4e35e8d2 100644 --- a/innobase/sync/sync0sync.c +++ b/innobase/sync/sync0sync.c @@ -220,7 +220,7 @@ mutex_create_func( char* cfile_name, /* in: file name where created */ ulint cline) /* in: file line where created */ { -#ifdef _WIN32 +#if defined(_WIN32) && defined(UNIV_CAN_USE_X86_ASSEMBLER) mutex_reset_lock_word(mutex); #else os_fast_mutex_init(&(mutex->os_fast_mutex)); @@ -273,7 +273,7 @@ mutex_free( mutex_exit(&mutex_list_mutex); -#ifndef _WIN32 +#if !defined(_WIN32) || !defined(UNIV_CAN_USE_X86_ASSEMBLER) os_fast_mutex_free(&(mutex->os_fast_mutex)); #endif /* If we free the mutex protecting the mutex list (freeing is @@ -1009,7 +1009,7 @@ sync_thread_add_level( } else if (level == SYNC_ANY_LATCH) { ut_a(sync_thread_levels_g(array, SYNC_ANY_LATCH)); } else if (level == SYNC_TRX_SYS_HEADER) { - ut_a(sync_thread_levels_contain(array, SYNC_KERNEL)); + ut_a(sync_thread_levels_g(array, SYNC_TRX_SYS_HEADER)); } else if (level == SYNC_DOUBLEWRITE) { ut_a(sync_thread_levels_g(array, SYNC_DOUBLEWRITE)); } else if (level == SYNC_BUF_BLOCK) { diff --git a/innobase/trx/trx0sys.c b/innobase/trx/trx0sys.c index 6c9776560bd..32a1db48488 100644 --- a/innobase/trx/trx0sys.c +++ b/innobase/trx/trx0sys.c @@ -438,7 +438,6 @@ trx_sys_update_mysql_binlog_offset( trx_sysf_t* sys_header; char namebuf[TRX_SYS_MYSQL_LOG_NAME_LEN]; - ut_ad(mutex_own(&kernel_mutex)); ut_ad(trx->mysql_log_file_name); memset(namebuf, ' ', TRX_SYS_MYSQL_LOG_NAME_LEN - 1); @@ -524,7 +523,7 @@ trx_sys_print_mysql_binlog_offset(void) } fprintf(stderr, - "InnoDB: Last MySQL binlog file offset %lu %lu, file name %s\n", + "InnoDB: Last MySQL binlog file position %lu %lu, file name %s\n", mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO + TRX_SYS_MYSQL_LOG_OFFSET_HIGH), mach_read_from_4(sys_header + TRX_SYS_MYSQL_LOG_INFO diff --git a/innobase/trx/trx0trx.c b/innobase/trx/trx0trx.c index 6cbf97104b3..ae607c33fbe 100644 --- a/innobase/trx/trx0trx.c +++ b/innobase/trx/trx0trx.c @@ -71,6 +71,9 @@ trx_create( trx->type = TRX_USER; trx->conc_state = TRX_NOT_STARTED; + trx->check_foreigns = TRUE; + trx->check_unique_secondary = TRUE; + trx->dict_operation = FALSE; trx->mysql_thd = NULL; @@ -113,6 +116,7 @@ trx_create( trx->lock_heap = mem_heap_create_in_buffer(256); UT_LIST_INIT(trx->trx_locks); + trx->has_dict_foreign_key_check_lock = FALSE; trx->has_search_latch = FALSE; trx->search_latch_timeout = BTR_SEA_TIMEOUT; @@ -703,8 +707,7 @@ trx_commit_off_kernel( /*-------------------------------------*/ - /* Only in some performance tests the variable srv_flush.. - will be set to FALSE: */ + /* Most MySQL users run with srv_flush.. set to FALSE: */ if (srv_flush_log_at_trx_commit) { |