diff options
Diffstat (limited to 'sql/sql_cache.cc')
-rw-r--r-- | sql/sql_cache.cc | 692 |
1 files changed, 367 insertions, 325 deletions
diff --git a/sql/sql_cache.cc b/sql/sql_cache.cc index b4a5020275a..ad0472cfc2c 100644 --- a/sql/sql_cache.cc +++ b/sql/sql_cache.cc @@ -1,5 +1,5 @@ -/* - Copyright (c) 2000, 2012, Oracle and/or its affiliates. +/* Copyright (c) 2000, 2013, Oracle and/or its affiliates. + Copyright (c) 2010, 2013, Monty Program Ab This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -12,8 +12,7 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* Description of the query cache: @@ -329,13 +328,23 @@ TODO list: (This could be done with almost no speed penalty) */ -#include "mysql_priv.h" +#include "my_global.h" /* NO_EMBEDDED_ACCESS_CHECKS */ +#include "sql_priv.h" +#include "sql_cache.h" +#include "sql_parse.h" // check_table_access +#include "tztime.h" // struct Time_zone +#include "sql_acl.h" // SELECT_ACL +#include "sql_base.h" // TMP_TABLE_KEY_EXTRA +#include "debug_sync.h" // DEBUG_SYNC #ifdef HAVE_QUERY_CACHE #include <m_ctype.h> #include <my_dir.h> #include <hash.h> #include "../storage/myisammrg/ha_myisammrg.h" #include "../storage/myisammrg/myrg_def.h" +#include "probes_mysql.h" +#include "log_slow.h" +#include "transaction.h" const uchar *query_state_map; @@ -344,18 +353,14 @@ const uchar *query_state_map; #endif #if !defined(EXTRA_DBUG) && !defined(DBUG_OFF) -#define MUTEX_LOCK(M) { DBUG_PRINT("lock", ("mutex lock 0x%lx", (ulong)(M))); \ - pthread_mutex_lock(M);} -#define MUTEX_UNLOCK(M) {DBUG_PRINT("lock", ("mutex unlock 0x%lx",\ - (ulong)(M))); pthread_mutex_unlock(M);} #define RW_WLOCK(M) {DBUG_PRINT("lock", ("rwlock wlock 0x%lx",(ulong)(M))); \ - if (!rw_wrlock(M)) DBUG_PRINT("lock", ("rwlock wlock ok")); \ + if (!mysql_rwlock_wrlock(M)) DBUG_PRINT("lock", ("rwlock wlock ok")); \ else DBUG_PRINT("lock", ("rwlock wlock FAILED %d", errno)); } #define RW_RLOCK(M) {DBUG_PRINT("lock", ("rwlock rlock 0x%lx", (ulong)(M))); \ - if (!rw_rdlock(M)) DBUG_PRINT("lock", ("rwlock rlock ok")); \ + if (!mysql_rwlock_rdlock(M)) DBUG_PRINT("lock", ("rwlock rlock ok")); \ else DBUG_PRINT("lock", ("rwlock wlock FAILED %d", errno)); } #define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock 0x%lx",(ulong)(M))); \ - if (!rw_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \ + if (!mysql_rwlock_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \ else DBUG_PRINT("lock", ("rwlock unlock FAILED %d", errno)); } #define BLOCK_LOCK_WR(B) {DBUG_PRINT("lock", ("%d LOCK_WR 0x%lx",\ __LINE__,(ulong)(B))); \ @@ -371,48 +376,10 @@ const uchar *query_state_map; __LINE__,(ulong)(B)));B->query()->unlock_reading();} #define DUMP(C) DBUG_EXECUTE("qcache", {\ (C)->cache_dump(); (C)->queries_dump();(C)->tables_dump();}) - - -/** - Causes the thread to wait in a spin lock for a query kill signal. - This function is used by the test frame work to identify race conditions. - - The signal is caught and ignored and the thread is not killed. -*/ - -static void debug_wait_for_kill(const char *info) -{ - const char *prev_info; - THD *thd; - char buff[1024]; - DBUG_ENTER("debug_wait_for_kill"); - - thd= current_thd; - prev_info= thd->proc_info; - thd->proc_info= info; - sql_print_information("%s", info); - while(!thd->killed) - my_sleep(1000); - thd->killed= NOT_KILLED; - /* - Remove the set debug variable, to ensure we don't get stuck on it again - This is needed as for MyISAM, invalidate_table() may be called twice - (Once from mysql_delete() and once from mi_update_status()) - */ - sprintf(buff, "-d,%s", info); - DBUG_SET(buff); - sql_print_information("Exit debug_wait_for_kill"); - thd->proc_info= prev_info; - - DBUG_VOID_RETURN; -} - #else -#define MUTEX_LOCK(M) pthread_mutex_lock(M) -#define MUTEX_UNLOCK(M) pthread_mutex_unlock(M) -#define RW_WLOCK(M) rw_wrlock(M) -#define RW_RLOCK(M) rw_rdlock(M) -#define RW_UNLOCK(M) rw_unlock(M) +#define RW_WLOCK(M) mysql_rwlock_wrlock(M) +#define RW_RLOCK(M) mysql_rwlock_rdlock(M) +#define RW_UNLOCK(M) mysql_rwlock_unlock(M) #define BLOCK_LOCK_WR(B) B->query()->lock_writing() #define BLOCK_LOCK_RD(B) B->query()->lock_reading() #define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing() @@ -420,10 +387,49 @@ static void debug_wait_for_kill(const char *info) #define DUMP(C) #endif -const char *query_cache_type_names[]= { "OFF", "ON", "DEMAND",NullS }; -TYPELIB query_cache_type_typelib= + +/** + Macro that executes the requested action at a synchronization point + only if the thread has a associated THD session. +*/ +#if defined(ENABLED_DEBUG_SYNC) +#define QC_DEBUG_SYNC(name) \ + do { \ + THD *thd= current_thd; \ + if (thd) \ + DEBUG_SYNC(thd, name); \ + } while (0) +#else +#define QC_DEBUG_SYNC(name) +#endif + + +/** + Thread state to be used when the query cache lock needs to be acquired. + Sets the thread state name in the constructor, resets on destructor. +*/ + +struct Query_cache_wait_state { - array_elements(query_cache_type_names)-1,"", query_cache_type_names, NULL + THD *m_thd; + const char *m_proc_info; + + Query_cache_wait_state(THD *thd, const char *func, + const char *file, unsigned int line) + : m_thd(thd), + m_proc_info(NULL) + { + if (m_thd) + m_proc_info= set_thd_proc_info(m_thd, + "Waiting for query cache lock", + func, file, line); + } + + ~Query_cache_wait_state() + { + if (m_thd) + set_thd_proc_info(m_thd, m_proc_info, NULL, NULL, 0); + } }; @@ -461,6 +467,8 @@ static void make_base_query(String *new_query, /* The following is guaranteed by the query_cache interface */ DBUG_ASSERT(query[query_length] == 0); DBUG_ASSERT(!is_white_space(query[0])); + /* We do not support UCS2, UTF16, UTF32 as a client character set */ + DBUG_ASSERT(current_thd->variables.character_set_client->mbminlen == 1); new_query->length(0); // Don't copy anything from old buffer if (new_query->realloc(query_length + additional_length)) @@ -585,24 +593,19 @@ void inline fix_local_query_cache_mode(THD *thd) bool Query_cache::try_lock(THD *thd, Cache_try_lock_mode mode) { bool interrupt= TRUE; - const char* old_proc_info; + Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__); DBUG_ENTER("Query_cache::try_lock"); - old_proc_info= thd->proc_info; - thd_proc_info(thd,"Waiting on query cache mutex"); - - pthread_mutex_lock(&structure_guard_mutex); - DBUG_EXECUTE_IF("status_wait_query_cache_mutex_sleep", { - sleep(5); - }); + mysql_mutex_lock(&structure_guard_mutex); + DBUG_EXECUTE_IF("status_wait_query_cache_mutex_sleep", { sleep(5); }); if (m_cache_status == DISABLED) { - pthread_mutex_unlock(&structure_guard_mutex); - thd->proc_info= old_proc_info; + mysql_mutex_unlock(&structure_guard_mutex); DBUG_RETURN(TRUE); } m_requests_in_progress++; fix_local_query_cache_mode(thd); + while (1) { if (m_cache_lock_status == Query_cache::UNLOCKED) @@ -631,14 +634,14 @@ bool Query_cache::try_lock(THD *thd, Cache_try_lock_mode mode) */ if (mode == WAIT) { - pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); + mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); } else if (mode == TIMEOUT) { struct timespec waittime; set_timespec_nsec(waittime,(ulong)(50000000L)); /* Wait for 50 msec */ - int res= pthread_cond_timedwait(&COND_cache_status_changed, - &structure_guard_mutex,&waittime); + int res= mysql_cond_timedwait(&COND_cache_status_changed, + &structure_guard_mutex, &waittime); if (res == ETIMEDOUT) break; } @@ -657,8 +660,7 @@ bool Query_cache::try_lock(THD *thd, Cache_try_lock_mode mode) } if (interrupt) m_requests_in_progress--; - pthread_mutex_unlock(&structure_guard_mutex); - thd->proc_info = old_proc_info; + mysql_mutex_unlock(&structure_guard_mutex); DBUG_RETURN(interrupt); } @@ -677,22 +679,23 @@ bool Query_cache::try_lock(THD *thd, Cache_try_lock_mode mode) void Query_cache::lock_and_suspend(void) { + THD *thd= current_thd; + Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__); DBUG_ENTER("Query_cache::lock_and_suspend"); - pthread_mutex_lock(&structure_guard_mutex); + mysql_mutex_lock(&structure_guard_mutex); m_requests_in_progress++; while (m_cache_lock_status != Query_cache::UNLOCKED) - pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); + mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); m_cache_lock_status= Query_cache::LOCKED_NO_WAIT; #ifndef DBUG_OFF /* Here thd may not be set during shutdown */ - THD *thd= current_thd; if (thd) m_cache_lock_thread_id= thd->thread_id; #endif /* Wake up everybody, a whole cache flush is starting! */ - pthread_cond_broadcast(&COND_cache_status_changed); - pthread_mutex_unlock(&structure_guard_mutex); + mysql_cond_broadcast(&COND_cache_status_changed); + mysql_mutex_unlock(&structure_guard_mutex); DBUG_VOID_RETURN; } @@ -707,18 +710,19 @@ void Query_cache::lock_and_suspend(void) void Query_cache::lock(THD *thd) { + Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__); DBUG_ENTER("Query_cache::lock"); - pthread_mutex_lock(&structure_guard_mutex); + mysql_mutex_lock(&structure_guard_mutex); m_requests_in_progress++; fix_local_query_cache_mode(thd); while (m_cache_lock_status != Query_cache::UNLOCKED) - pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); + mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex); m_cache_lock_status= Query_cache::LOCKED; #ifndef DBUG_OFF m_cache_lock_thread_id= thd->thread_id; #endif - pthread_mutex_unlock(&structure_guard_mutex); + mysql_mutex_unlock(&structure_guard_mutex); DBUG_VOID_RETURN; } @@ -731,7 +735,7 @@ void Query_cache::lock(THD *thd) void Query_cache::unlock(void) { DBUG_ENTER("Query_cache::unlock"); - pthread_mutex_lock(&structure_guard_mutex); + mysql_mutex_lock(&structure_guard_mutex); #ifndef DBUG_OFF /* Thd may not be set in resize() at mysqld start */ THD *thd= current_thd; @@ -742,7 +746,7 @@ void Query_cache::unlock(void) m_cache_lock_status == Query_cache::LOCKED_NO_WAIT); m_cache_lock_status= Query_cache::UNLOCKED; DBUG_PRINT("Query_cache",("Sending signal")); - pthread_cond_signal(&COND_cache_status_changed); + mysql_cond_signal(&COND_cache_status_changed); DBUG_ASSERT(m_requests_in_progress > 0); m_requests_in_progress--; if (m_requests_in_progress == 0 && m_cache_status == DISABLE_REQUEST) @@ -751,7 +755,7 @@ void Query_cache::unlock(void) free_cache(); m_cache_status= DISABLED; } - pthread_mutex_unlock(&structure_guard_mutex); + mysql_mutex_unlock(&structure_guard_mutex); DBUG_VOID_RETURN; } @@ -827,18 +831,18 @@ void Query_cache_block::destroy() DBUG_VOID_RETURN; } -inline uint Query_cache_block::headers_len() +uint Query_cache_block::headers_len() { return (ALIGN_SIZE(sizeof(Query_cache_block_table)*n_tables) + ALIGN_SIZE(sizeof(Query_cache_block))); } -inline uchar* Query_cache_block::data(void) +uchar* Query_cache_block::data(void) { return (uchar*)( ((uchar*)this) + headers_len() ); } -inline Query_cache_query * Query_cache_block::query() +Query_cache_query * Query_cache_block::query() { #ifndef DBUG_OFF if (type != QUERY) @@ -847,7 +851,7 @@ inline Query_cache_query * Query_cache_block::query() return (Query_cache_query *) data(); } -inline Query_cache_table * Query_cache_block::table() +Query_cache_table * Query_cache_block::table() { #ifndef DBUG_OFF if (type != TABLE) @@ -856,7 +860,7 @@ inline Query_cache_table * Query_cache_block::table() return (Query_cache_table *) data(); } -inline Query_cache_result * Query_cache_block::result() +Query_cache_result * Query_cache_block::result() { #ifndef DBUG_OFF if (type != RESULT && type != RES_CONT && type != RES_BEG && @@ -866,7 +870,7 @@ inline Query_cache_result * Query_cache_block::result() return (Query_cache_result *) data(); } -inline Query_cache_block_table * Query_cache_block::table(TABLE_COUNTER_TYPE n) +Query_cache_block_table * Query_cache_block::table(TABLE_COUNTER_TYPE n) { return ((Query_cache_block_table *) (((uchar*)this)+ALIGN_SIZE(sizeof(Query_cache_block)) + @@ -919,7 +923,7 @@ inline void Query_cache_query::lock_writing() bool Query_cache_query::try_lock_writing() { DBUG_ENTER("Query_cache_block::try_lock_writing"); - if (rw_trywrlock(&lock)!=0) + if (mysql_rwlock_trywrlock(&lock) != 0) { DBUG_PRINT("info", ("can't lock rwlock")); DBUG_RETURN(0); @@ -951,7 +955,7 @@ void Query_cache_query::init_n_lock() { DBUG_ENTER("Query_cache_query::init_n_lock"); res=0; wri = 0; len = 0; - my_rwlock_init(&lock, NULL); + mysql_rwlock_init(key_rwlock_query_cache_query_lock, &lock); lock_writing(); DBUG_PRINT("qcache", ("inited & locked query for block 0x%lx", (long) (((uchar*) this) - @@ -971,7 +975,7 @@ void Query_cache_query::unlock_n_destroy() active semaphore */ this->unlock_writing(); - rwlock_destroy(&lock); + mysql_rwlock_destroy(&lock); DBUG_VOID_RETURN; } @@ -997,19 +1001,20 @@ uchar *query_cache_query_get_key(const uchar *record, size_t *length, Note on double-check locking (DCL) usage. Below, in query_cache_insert(), query_cache_abort() and - query_cache_end_of_result() we use what is called double-check - locking (DCL) for NET::query_cache_query. I.e. we test it first - without a lock, and, if positive, test again under the lock. + Query_cache::end_of_result() we use what is called double-check + locking (DCL) for Query_cache_tls::first_query_block. + I.e. we test it first without a lock, and, if positive, test again + under the lock. - This means that if we see 'NET::query_cache_query == 0' without a + This means that if we see 'first_query_block == 0' without a lock we will skip the operation. But this is safe here: when we started to cache a query, we called Query_cache::store_query(), and - NET::query_cache_query was set to non-zero in this thread (and the + 'first_query_block' was set to non-zero in this thread (and the thread always sees results of its memory operations, mutex or not). - If later we see 'NET::query_cache_query == 0' without locking a + If later we see 'first_query_block == 0' without locking a mutex, that may only mean that some other thread have reset it by invalidating the query. Skipping the operation in this case is the - right thing to do, as NET::query_cache_query won't get non-zero for + right thing to do, as first_query_block won't get non-zero for this query again. See also comments in Query_cache::store_query() and @@ -1018,65 +1023,75 @@ uchar *query_cache_query_get_key(const uchar *record, size_t *length, NOTE, however, that double-check locking is not applicable in 'invalidate' functions, as we may erroneously skip invalidation, because the thread doing invalidation may never see non-zero - NET::query_cache_query. + 'first_query_block'. */ -void query_cache_init_query(NET *net) +/** + libmysql convenience wrapper to insert data into query cache. +*/ +void query_cache_insert(const char *packet, ulong length, + unsigned pkt_nr) { + THD *thd= current_thd; + /* - It is safe to initialize 'NET::query_cache_query' without a lock - here, because before it will be accessed from different threads it - will be set in this thread under a lock, and access from the same - thread is always safe. + Current_thd can be NULL when a new connection is immediately ended + due to "Too many connections". thd->store_globals() has not been + called at this time and hence my_pthread_setspecific_ptr(THR_THD, + this) has not been called for this thread. */ - net->query_cache_query= 0; + + if (!thd) + return; + + query_cache.insert(&thd->query_cache_tls, + packet, length, + pkt_nr); } -/* +/** Insert the packet into the query cache. */ -void query_cache_insert(NET *net, const char *packet, ulong length) +void +Query_cache::insert(Query_cache_tls *query_cache_tls, + const char *packet, ulong length, + unsigned pkt_nr) { - DBUG_ENTER("query_cache_insert"); + DBUG_ENTER("Query_cache::insert"); - if (net->query_cache_query == 0) + /* First we check if query cache is disable without doing a mutex lock */ + if (is_disabled() || query_cache_tls->first_query_block == NULL) DBUG_VOID_RETURN; DBUG_ASSERT(current_thd); - DBUG_EXECUTE_IF("wait_in_query_cache_insert", - debug_wait_for_kill("wait_in_query_cache_insert"); ); - - /* First we check if query cache is disable without doing a mutex lock */ - if(query_cache.is_disabled()) - DBUG_VOID_RETURN; + QC_DEBUG_SYNC("wait_in_query_cache_insert"); /* Lock the cache with try_lock(). try_lock() will fail if cache was disabled between the above test and lock. */ - if (query_cache.try_lock(current_thd, Query_cache::WAIT)) + if (try_lock(current_thd, Query_cache::WAIT)) DBUG_VOID_RETURN; - Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query; - if (!query_block) + Query_cache_block *query_block = query_cache_tls->first_query_block; + if (query_block == NULL) { /* We lost the writer and the currently processed query has been invalidated; there is nothing left to do. */ - query_cache.unlock(); + unlock(); DBUG_VOID_RETURN; } - BLOCK_LOCK_WR(query_block); Query_cache_query *header= query_block->query(); Query_cache_block *result= header->result(); - DUMP(&query_cache); + DUMP(this); DBUG_PRINT("qcache", ("insert packet %lu bytes long",length)); /* @@ -1084,8 +1099,8 @@ void query_cache_insert(NET *net, const char *packet, ulong length) still need structure_guard_mutex to free the query, and therefore unlock it later in this function. */ - if (!query_cache.append_result_data(&result, length, (uchar*) packet, - query_block)) + if (!append_result_data(&result, length, (uchar*) packet, + query_block)) { DBUG_PRINT("warning", ("Can't append data")); header->result(result); @@ -1094,89 +1109,84 @@ void query_cache_insert(NET *net, const char *packet, ulong length) query_cache.free_query(query_block); query_cache.refused++; // append_result_data no success => we need unlock - query_cache.unlock(); + unlock(); DBUG_VOID_RETURN; } header->result(result); - header->last_pkt_nr= net->pkt_nr; + header->last_pkt_nr= pkt_nr; BLOCK_UNLOCK_WR(query_block); - DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0);); + DBUG_EXECUTE("check_querycache",check_integrity(0);); DBUG_VOID_RETURN; } -void query_cache_abort(NET *net) +void +Query_cache::abort(Query_cache_tls *query_cache_tls) { THD *thd; DBUG_ENTER("query_cache_abort"); /* See the comment on double-check locking usage above. */ - if (net->query_cache_query == 0) + if (is_disabled() || query_cache_tls->first_query_block == NULL) DBUG_VOID_RETURN; - if (query_cache.try_lock(current_thd, Query_cache::WAIT)) - { - net->query_cache_query = 0; + if (try_lock(current_thd, Query_cache::WAIT)) DBUG_VOID_RETURN; - } /* While we were waiting another thread might have changed the status of the writer. Make sure the writer still exists before continue. */ - Query_cache_block *query_block= ((Query_cache_block*) - net->query_cache_query); + Query_cache_block *query_block= query_cache_tls->first_query_block; if (query_block) { thd= current_thd; thd_proc_info(thd, "storing result in query cache"); - DUMP(&query_cache); + DUMP(this); BLOCK_LOCK_WR(query_block); // The following call will remove the lock on query_block - query_cache.free_query(query_block); - net->query_cache_query= 0; - DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1);); + free_query(query_block); + query_cache_tls->first_query_block= NULL; + DBUG_EXECUTE("check_querycache", check_integrity(1);); } - DBUG_ASSERT(!net->query_cache_query); - query_cache.unlock(); + unlock(); + DBUG_VOID_RETURN; } -void query_cache_end_of_result(THD *thd) +void Query_cache::end_of_result(THD *thd) { Query_cache_block *query_block; - DBUG_ENTER("query_cache_end_of_result"); + Query_cache_tls *query_cache_tls= &thd->query_cache_tls; + ulonglong limit_found_rows= thd->limit_found_rows; + DBUG_ENTER("Query_cache::end_of_result"); /* See the comment on double-check locking usage above. */ - if (thd->net.query_cache_query == 0) + if (query_cache_tls->first_query_block == NULL) DBUG_VOID_RETURN; /* Ensure that only complete results are cached. */ - DBUG_ASSERT(thd->main_da.is_eof()); + DBUG_ASSERT(thd->stmt_da->is_eof()); if (thd->killed) { - query_cache_abort(&thd->net); + query_cache_abort(&thd->query_cache_tls); DBUG_VOID_RETURN; } #ifdef EMBEDDED_LIBRARY - query_cache_insert(&thd->net, (char*)thd, - emb_count_querycache_size(thd)); + insert(query_cache_tls, (char*)thd, + emb_count_querycache_size(thd), 0); #endif - if (query_cache.try_lock(thd, Query_cache::WAIT)) - { - thd->net.query_cache_query= 0; + if (try_lock(thd, Query_cache::WAIT)) DBUG_VOID_RETURN; - } - /* thd->net.query_cache_query may have changed during resize */ - query_block= ((Query_cache_block*) thd->net.query_cache_query); + query_block= query_cache_tls->first_query_block; if (query_block) { /* @@ -1185,7 +1195,7 @@ void query_cache_end_of_result(THD *thd) block, the writer should be dropped. */ thd_proc_info(thd, "storing result in query cache"); - DUMP(&query_cache); + DUMP(this); BLOCK_LOCK_WR(query_block); Query_cache_query *header= query_block->query(); Query_cache_block *last_result_block; @@ -1201,9 +1211,9 @@ void query_cache_end_of_result(THD *thd) to this function. In the release version that query should be ignored and removed from QC. */ - query_cache.free_query(query_block); - thd->net.query_cache_query= 0; - query_cache.unlock(); + DBUG_ASSERT(0); + free_query(query_block); + unlock(); DBUG_VOID_RETURN; } last_result_block= header->result()->prev; @@ -1212,17 +1222,17 @@ void query_cache_end_of_result(THD *thd) if (last_result_block->length >= query_cache.min_allocation_unit + len) query_cache.split_block(last_result_block,len); - header->found_rows(current_thd->limit_found_rows); + header->found_rows(limit_found_rows); header->result()->type= Query_cache_block::RESULT; /* Drop the writer. */ header->writer(0); - thd->net.query_cache_query= 0; + query_cache_tls->first_query_block= NULL; BLOCK_UNLOCK_WR(query_block); - DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1);); - + DBUG_EXECUTE("check_querycache", check_integrity(1);); } - query_cache.unlock(); + + unlock(); DBUG_VOID_RETURN; } @@ -1284,6 +1294,7 @@ ulong Query_cache::resize(ulong query_cache_size_arg) if (global_system_variables.query_cache_type == 0) { + DBUG_ASSERT(query_cache_size_arg == 0); if (query_cache_size_arg != 0) my_error(ER_QUERY_CACHE_IS_DISABLED, MYF(0)); DBUG_RETURN(0); @@ -1302,19 +1313,20 @@ ulong Query_cache::resize(ulong query_cache_size_arg) { BLOCK_LOCK_WR(block); Query_cache_query *query= block->query(); - if (query && query->writer()) + if (query->writer()) { /* - Drop the writer; this will cancel any attempts to store + Drop the writer; this will cancel any attempts to store the processed statement associated with this writer. */ - query->writer()->query_cache_query= 0; + query->writer()->first_query_block= NULL; query->writer(0); refused++; } - BLOCK_UNLOCK_WR(block); + query->unlock_n_destroy(); block= block->next; } while (block != queries_blocks); + queries_blocks= NULL; // avoid second destroying by free_cache } free_cache(); @@ -1340,9 +1352,10 @@ ulong Query_cache::resize(ulong query_cache_size_arg) ulong Query_cache::set_min_res_unit(ulong size) { + DBUG_ASSERT(size % 8 == 0); if (size < min_allocation_unit) - size= min_allocation_unit; - return (min_result_data_size= ALIGN_SIZE(size)); + size= ALIGN_SIZE(min_allocation_unit); + return (min_result_data_size= size); } @@ -1393,10 +1406,12 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) protocol (COM_EXECUTE) cannot be served to statements asking for results in the text protocol (COM_QUERY) and vice-versa. */ - flags.result_in_binary_protocol= (unsigned int) thd->protocol->type(); + flags.protocol_type= (unsigned int) thd->protocol->type(); + /* PROTOCOL_LOCAL results are not cached. */ + DBUG_ASSERT(flags.protocol_type != (unsigned int) Protocol::PROTOCOL_LOCAL); flags.more_results_exists= test(thd->server_status & SERVER_MORE_RESULTS_EXISTS); - flags.in_trans= test(thd->server_status & SERVER_STATUS_IN_TRANS); + flags.in_trans= thd->in_active_multi_stmt_transaction(); flags.autocommit= test(thd->server_status & SERVER_STATUS_AUTOCOMMIT); flags.pkt_nr= net->pkt_nr; flags.character_set_client_num= @@ -1418,11 +1433,11 @@ void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used) DBUG_PRINT("qcache", ("\ long %d, 4.1: %d, bin_proto: %d, more results %d, pkt_nr: %d, \ CS client: %u, CS result: %u, CS conn: %u, limit: %lu, TZ: 0x%lx, \ -sql mode: 0x%lx, sort len: %lu, conncat len: %lu, div_precision: %lu, \ +sql mode: 0x%llx, sort len: %lu, conncat len: %lu, div_precision: %lu, \ def_week_frmt: %lu, in_trans: %d, autocommit: %d", (int)flags.client_long_flag, (int)flags.client_protocol_41, - (int)flags.result_in_binary_protocol, + (int)flags.protocol_type, (int)flags.more_results_exists, flags.pkt_nr, flags.character_set_client_num, @@ -1497,7 +1512,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", /* Check if another thread is processing the same query? */ Query_cache_block *competitor = (Query_cache_block *) - hash_search(&queries, (uchar*) query, tot_length); + my_hash_search(&queries, (uchar*) query, tot_length); DBUG_PRINT("qcache", ("competitor 0x%lx", (ulong) competitor)); if (competitor == 0) { @@ -1522,11 +1537,11 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", unlock(); goto end; } - if (!register_all_tables(query_block, tables_used, local_tables)) + if (!register_all_tables(thd, query_block, tables_used, local_tables)) { refused++; DBUG_PRINT("warning", ("tables list including failed")); - hash_delete(&queries, (uchar *) query_block); + my_hash_delete(&queries, (uchar *) query_block); header->unlock_n_destroy(); free_memory_block(query_block); unlock(); @@ -1535,8 +1550,8 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", double_linked_list_simple_include(query_block, &queries_blocks); inserts++; queries_in_cache++; - net->query_cache_query= (uchar*) query_block; - header->writer(net); + thd->query_cache_tls.first_query_block= query_block; + header->writer(&thd->query_cache_tls); header->tables_type(tables_type); unlock(); @@ -1623,14 +1638,18 @@ send_data_in_chunks(NET *net, const uchar *packet, ulong len) Check if the query is in the cache. If it was cached, send it to the user. - RESULTS - 0 Query was not cached. - 1 The query was cached and user was sent the result. - -1 The query was cached but we didn't have rights to use it. - No error is sent to the client yet. + @param thd Pointer to the thread handler + @param org_sql A pointer to the sql statement * + @param query_length Length of the statement in characters - NOTE - This method requires that sql points to allocated memory of size: + @return status code + @retval 0 Query was not cached. + @retval 1 The query was cached and user was sent the result. + @retval -1 The query was cached but we didn't have rights to use it. + + In case of -1, no error is sent to the client. + + *) The buffer must be allocated memory of size: tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE; */ @@ -1650,13 +1669,13 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) DBUG_ENTER("Query_cache::send_result_to_client"); /* - Testing 'query_cache_size' without a lock here is safe: the thing + Testing without a lock here is safe: the thing we may loose is that the query won't be served from cache, but we save on mutex locking in the case when query cache is disabled. See also a note on double-check locking usage above. */ - if (is_disabled() || thd->locked_tables || + if (is_disabled() || thd->locked_tables_mode || thd->variables.query_cache_type == 0) goto err; @@ -1670,8 +1689,6 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) goto err; } - DBUG_ASSERT(query_cache_size != 0); // otherwise cache would be disabled - thd->query_cache_is_applicable= 1; sql= org_sql; sql_end= sql + query_length; @@ -1798,16 +1815,13 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) goto err; if (query_cache_size == 0) + { + thd->query_cache_is_applicable= 0; // Query can't be cached goto err_unlock; - - /* - Check that we haven't forgot to reset the query cache variables; - make sure there are no attached query cache writer to this thread. - */ - DBUG_ASSERT(thd->net.query_cache_query == 0); + } Query_cache_block *query_block; - if (opt_query_cache_strip_comments) + if (thd->variables.query_cache_strip_comments) { if (found_brace) sql= found_brace; @@ -1845,10 +1859,10 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) flags.client_long_flag= test(thd->client_capabilities & CLIENT_LONG_FLAG); flags.client_protocol_41= test(thd->client_capabilities & CLIENT_PROTOCOL_41); - flags.result_in_binary_protocol= (unsigned int)thd->protocol->type(); + flags.protocol_type= (unsigned int) thd->protocol->type(); flags.more_results_exists= test(thd->server_status & SERVER_MORE_RESULTS_EXISTS); - flags.in_trans= test(thd->server_status & SERVER_STATUS_IN_TRANS); + flags.in_trans= thd->in_active_multi_stmt_transaction(); flags.autocommit= test(thd->server_status & SERVER_STATUS_AUTOCOMMIT); flags.pkt_nr= thd->net.pkt_nr; flags.character_set_client_num= thd->variables.character_set_client->number; @@ -1868,11 +1882,11 @@ Query_cache::send_result_to_client(THD *thd, char *org_sql, uint query_length) DBUG_PRINT("qcache", ("\ long %d, 4.1: %d, bin_proto: %d, more results %d, pkt_nr: %d, \ CS client: %u, CS result: %u, CS conn: %u, limit: %lu, TZ: 0x%lx, \ -sql mode: 0x%lx, sort len: %lu, conncat len: %lu, div_precision: %lu, \ +sql mode: 0x%llx, sort len: %lu, conncat len: %lu, div_precision: %lu, \ def_week_frmt: %lu, in_trans: %d, autocommit: %d", (int)flags.client_long_flag, (int)flags.client_protocol_41, - (int)flags.result_in_binary_protocol, + (int)flags.protocol_type, (int)flags.more_results_exists, flags.pkt_nr, flags.character_set_client_num, @@ -1889,8 +1903,8 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", (int)flags.autocommit)); memcpy((uchar *)(sql + (tot_length - QUERY_CACHE_FLAGS_SIZE)), (uchar*) &flags, QUERY_CACHE_FLAGS_SIZE); - query_block = (Query_cache_block *) hash_search(&queries, (uchar*) sql, - tot_length); + query_block = (Query_cache_block *) my_hash_search(&queries, (uchar*) sql, + tot_length); /* Quick abort on unlocked data */ if (query_block == 0 || query_block->query()->result() == 0 || @@ -1919,7 +1933,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } DBUG_PRINT("qcache", ("Query have result 0x%lx", (ulong) query)); - if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + if (thd->in_multi_stmt_transaction_mode() && (query->tables_type() & HA_CACHE_TBL_TRANSACT)) { DBUG_PRINT("qcache", @@ -1971,7 +1985,7 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", table_list.db = table->db(); table_list.alias= table_list.table_name= table->table(); #ifndef NO_EMBEDDED_ACCESS_CHECKS - if (check_table_access(thd,SELECT_ACL,&table_list, 1, TRUE)) + if (check_table_access(thd,SELECT_ACL,&table_list, FALSE, 1,TRUE)) { DBUG_PRINT("qcache", ("probably no SELECT access to %s.%s => return to normal processing", @@ -2019,6 +2033,13 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", */ thd->query_cache_is_applicable= 0; // Query can't be cached } + /* + End the statement transaction potentially started by engine. + Currently our engines do not request rollback from callbacks. + If this is going to change code needs to be reworked. + */ + DBUG_ASSERT(! thd->transaction_rollback_request); + trans_rollback_stmt(thd); goto err_unlock; // Parse query } else @@ -2058,17 +2079,31 @@ def_week_frmt: %lu, in_trans: %d, autocommit: %d", } #endif /*!EMBEDDED_LIBRARY*/ - thd->limit_found_rows = query->found_rows(); + thd->sent_row_count= thd->limit_found_rows = query->found_rows(); thd->status_var.last_query_cost= 0.0; thd->query_plan_flags= (thd->query_plan_flags & ~QPLAN_QC_NO) | QPLAN_QC; - if (!thd->main_da.is_set()) - thd->main_da.disable_status(); + if (!thd->sent_row_count) + status_var_increment(thd->status_var.empty_queries); + else + status_var_add(thd->status_var.rows_sent, thd->sent_row_count); + + /* + End the statement transaction potentially started by an + engine callback. We ignore the return value for now, + since as long as EOF packet is part of the query cache + response, we can't handle it anyway. + */ + (void) trans_commit_stmt(thd); + if (!thd->stmt_da->is_set()) + thd->stmt_da->disable_status(); BLOCK_UNLOCK_RD(query_block); + MYSQL_QUERY_CACHE_HIT(thd->query(), (ulong) thd->limit_found_rows); DBUG_RETURN(1); // Result sent to client err_unlock: unlock(); + MYSQL_QUERY_CACHE_MISS(thd->query()); /* query_plan_flags doesn't have to be changed here as it contains QPLAN_QC_NO by default @@ -2092,8 +2127,7 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction_mode(); for (; tables_used; tables_used= tables_used->next_local) { DBUG_ASSERT(!using_transactions || tables_used->table!=0); @@ -2112,8 +2146,7 @@ void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used, invalidate_table(thd, tables_used); } - DBUG_EXECUTE_IF("wait_after_query_cache_invalidate", - debug_wait_for_kill("wait_after_query_cache_invalidate");); + DEBUG_SYNC(thd, "wait_after_query_cache_invalidate"); DBUG_VOID_RETURN; } @@ -2176,8 +2209,7 @@ void Query_cache::invalidate(THD *thd, TABLE *table, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction_mode(); if (using_transactions && (table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT)) thd->add_changed_table(table); @@ -2195,8 +2227,7 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length, if (is_disabled()) DBUG_VOID_RETURN; - using_transactions= using_transactions && - (thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)); + using_transactions= using_transactions && thd->in_multi_stmt_transaction_mode(); if (using_transactions) // used for innodb => has_transactions() is TRUE thd->add_changed_table(key, key_length); else @@ -2212,12 +2243,11 @@ void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length, void Query_cache::invalidate(THD *thd, char *db) { - bool restart; DBUG_ENTER("Query_cache::invalidate (db)"); if (is_disabled()) DBUG_VOID_RETURN; - restart= FALSE; + bool restart= FALSE; /* Lock the query cache and queue all invalidation attempts to avoid the risk of a race between invalidation, cache inserts and flushes. @@ -2303,8 +2333,7 @@ void Query_cache::flush() if (is_disabled()) DBUG_VOID_RETURN; - DBUG_EXECUTE_IF("wait_in_query_cache_flush1", - debug_wait_for_kill("wait_in_query_cache_flush1");); + QC_DEBUG_SYNC("wait_in_query_cache_flush1"); lock_and_suspend(); if (query_cache_size > 0) @@ -2334,6 +2363,9 @@ void Query_cache::pack(THD *thd, ulong join_limit, uint iteration_limit) { DBUG_ENTER("Query_cache::pack"); + if (is_disabled()) + DBUG_VOID_RETURN; + /* If the entire qc is being invalidated we can bail out early instead of waiting for the lock. @@ -2372,8 +2404,8 @@ void Query_cache::destroy() free_cache(); unlock(); - pthread_cond_destroy(&COND_cache_status_changed); - pthread_mutex_destroy(&structure_guard_mutex); + mysql_cond_destroy(&COND_cache_status_changed); + mysql_mutex_destroy(&structure_guard_mutex); initialized = 0; DBUG_ASSERT(m_requests_in_progress == 0); } @@ -2401,13 +2433,36 @@ void Query_cache::disable_query_cache(THD *thd) void Query_cache::init() { DBUG_ENTER("Query_cache::init"); - pthread_mutex_init(&structure_guard_mutex,MY_MUTEX_INIT_FAST); - pthread_cond_init(&COND_cache_status_changed, NULL); + mysql_mutex_init(key_structure_guard_mutex, + &structure_guard_mutex, MY_MUTEX_INIT_FAST); + mysql_cond_init(key_COND_cache_status_changed, + &COND_cache_status_changed, NULL); m_cache_lock_status= Query_cache::UNLOCKED; m_cache_status= Query_cache::OK; m_requests_in_progress= 0; initialized = 1; - query_state_map= default_charset_info->state_map; + /* + Using state_map from latin1 should be fine in all cases: + 1. We do not support UCS2, UTF16, UTF32 as a client character set. + 2. The other character sets are compatible on the lower ASCII-range + 0x00-0x20, and have the following characters marked as spaces: + + 0x09 TAB + 0x0A LINE FEED + 0x0B VERTICAL TAB + 0x0C FORM FEED + 0x0D CARRIAGE RETUR + 0x20 SPACE + + Additionally, only some of the ASCII-compatible character sets + (including latin1) can have 0xA0 mapped to "NON-BREAK SPACE" + and thus marked as space. + That should not be a problem for those charsets that map 0xA0 + to something else: the parser will just return syntax error + if this character appears straight in the query + (i.e. not inside a string literal or comment). + */ + query_state_map= my_charset_latin1.state_map; /* If we explicitly turn off query cache from the command line query cache will be disabled for the reminder of the server life @@ -2557,9 +2612,9 @@ ulong Query_cache::init_cache() DUMP(this); - VOID(hash_init(&queries, &my_charset_bin, def_query_hash_size, 0, 0, - query_cache_query_get_key, 0, 0)); -#ifndef FN_NO_CASE_SENCE + (void) my_hash_init(&queries, &my_charset_bin, def_query_hash_size, 0, 0, + query_cache_query_get_key, 0, 0); +#ifndef FN_NO_CASE_SENSE /* If lower_case_table_names!=0 then db and table names are already converted to lower case and we can use binary collation for their @@ -2568,8 +2623,8 @@ ulong Query_cache::init_cache() lower_case_table_names == 0 then we should distinguish my_table and MY_TABLE cases and so again can use binary collation. */ - VOID(hash_init(&tables, &my_charset_bin, def_table_hash_size, 0, 0, - query_cache_table_get_key, 0, 0)); + (void) my_hash_init(&tables, &my_charset_bin, def_table_hash_size, 0, 0, + query_cache_table_get_key, 0, 0); #else /* On windows, OS/2, MacOS X with HFS+ or any other case insensitive @@ -2579,10 +2634,11 @@ ulong Query_cache::init_cache() file system) and so should use case insensitive collation for comparison. */ - VOID(hash_init(&tables, - lower_case_table_names ? &my_charset_bin : - files_charset_info, - def_table_hash_size, 0, 0,query_cache_table_get_key, 0, 0)); + (void) my_hash_init(&tables, + lower_case_table_names ? &my_charset_bin : + files_charset_info, + def_table_hash_size, 0, 0,query_cache_table_get_key, + 0, 0); #endif queries_in_cache = 0; @@ -2637,15 +2693,15 @@ void Query_cache::free_cache() do { Query_cache_query *query= block->query(); - rwlock_destroy(&query->lock); + mysql_rwlock_destroy(&query->lock); block= block->next; } while (block != queries_blocks); } - my_free((uchar*) cache, MYF(MY_ALLOW_ZERO_PTR)); + my_free(cache); make_disabled(); - hash_free(&queries); - hash_free(&tables); + my_hash_free(&queries); + my_hash_free(&tables); DBUG_VOID_RETURN; } @@ -2669,9 +2725,7 @@ void Query_cache::free_cache() void Query_cache::flush_cache() { - - DBUG_EXECUTE_IF("wait_in_query_cache_flush2", - debug_wait_for_kill("wait_in_query_cache_flush2");); + QC_DEBUG_SYNC("wait_in_query_cache_flush2"); my_hash_reset(&queries); while (queries_blocks != 0) @@ -2757,7 +2811,7 @@ void Query_cache::free_query_internal(Query_cache_block *query_block) if (query->writer() != 0) { /* Tell MySQL that this query should not be cached anymore */ - query->writer()->query_cache_query= 0; + query->writer()->first_query_block= NULL; query->writer(0); } double_linked_list_exclude(query_block, &queries_blocks); @@ -2820,7 +2874,7 @@ void Query_cache::free_query(Query_cache_block *query_block) (ulong) query_block, query_block->query()->length() )); - hash_delete(&queries,(uchar *) query_block); + my_hash_delete(&queries,(uchar *) query_block); free_query_internal(query_block); DBUG_VOID_RETURN; @@ -3117,8 +3171,7 @@ void Query_cache::invalidate_table(THD *thd, TABLE *table) void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length) { - DBUG_EXECUTE_IF("wait_in_query_cache_invalidate1", - debug_wait_for_kill("wait_in_query_cache_invalidate1"); ); + DEBUG_SYNC(thd, "wait_in_query_cache_invalidate1"); /* Lock the query cache and queue all invalidation attempts to avoid @@ -3126,9 +3179,7 @@ void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length) */ lock(thd); - DBUG_EXECUTE_IF("wait_in_query_cache_invalidate2", - debug_wait_for_kill("wait_in_query_cache_invalidate2"); ); - + DEBUG_SYNC(thd, "wait_in_query_cache_invalidate2"); if (query_cache_size > 0) invalidate_table_internal(thd, key, key_length); @@ -3149,7 +3200,7 @@ void Query_cache::invalidate_table_internal(THD *thd, uchar *key, uint32 key_length) { Query_cache_block *table_block= - (Query_cache_block*)hash_search(&tables, key, key_length); + (Query_cache_block*)my_hash_search(&tables, key, key_length); if (table_block) { Query_cache_block_table *list_root= table_block->table(0); @@ -3178,7 +3229,6 @@ Query_cache::invalidate_query_block_list(THD *thd, Query_cache_block *query_block= list_root->next->block(); BLOCK_LOCK_WR(query_block); free_query(query_block); - DBUG_EXECUTE_IF("debug_cache_locks", sleep(10);); } } @@ -3188,6 +3238,7 @@ Query_cache::invalidate_query_block_list(THD *thd, SYNOPSIS Query_cache::register_tables_from_list + thd thread handle tables_used given table list counter number current position in table of tables of block block_table pointer to current position in tables table of block @@ -3198,24 +3249,24 @@ Query_cache::invalidate_query_block_list(THD *thd, */ TABLE_COUNTER_TYPE -Query_cache::register_tables_from_list(TABLE_LIST *tables_used, +Query_cache::register_tables_from_list(THD *thd, TABLE_LIST *tables_used, TABLE_COUNTER_TYPE counter, - Query_cache_block_table *block_table) + Query_cache_block_table **block_table) { TABLE_COUNTER_TYPE n; DBUG_ENTER("Query_cache::register_tables_from_list"); for (n= counter; tables_used; - tables_used= tables_used->next_global, n++, block_table++) + tables_used= tables_used->next_global, n++, (*block_table)++) { if (tables_used->is_anonymous_derived_table()) { DBUG_PRINT("qcache", ("derived table skipped")); n--; - block_table--; + (*block_table)--; continue; } - block_table->n= n; + (*block_table)->n= n; if (tables_used->view) { char key[MAX_DBKEY_LENGTH]; @@ -3228,9 +3279,9 @@ Query_cache::register_tables_from_list(TABLE_LIST *tables_used, /* There are not callback function for for VIEWs */ - if (!insert_table(key_length, key, block_table, - tables_used->view_db.length + 1, - HA_CACHE_TBL_NONTRANSACT, 0, 0)) + if (!insert_table(key_length, key, (*block_table), + tables_used->view_db.length, + HA_CACHE_TBL_NONTRANSACT, 0, 0, TRUE)) DBUG_RETURN(0); /* We do not need to register view tables here because they are already @@ -3249,42 +3300,17 @@ Query_cache::register_tables_from_list(TABLE_LIST *tables_used, if (!insert_table(tables_used->table->s->table_cache_key.length, tables_used->table->s->table_cache_key.str, - block_table, + (*block_table), tables_used->db_length, tables_used->table->file->table_cache_type(), tables_used->callback_func, - tables_used->engine_data)) + tables_used->engine_data, + TRUE)) DBUG_RETURN(0); -#ifdef WITH_MYISAMMRG_STORAGE_ENGINE - /* - XXX FIXME: Some generic mechanism is required here instead of this - MYISAMMRG-specific implementation. - */ - if (tables_used->table->s->db_type()->db_type == DB_TYPE_MRG_MYISAM) - { - ha_myisammrg *handler = (ha_myisammrg *) tables_used->table->file; - MYRG_INFO *file = handler->myrg_info(); - for (MYRG_TABLE *table = file->open_tables; - table != file->end_table ; - table++) - { - char key[MAX_DBKEY_LENGTH]; - uint32 db_length; - uint key_length= filename_2_table_key(key, table->table->filename, - &db_length); - (++block_table)->n= ++n; - /* - There are not callback function for for MyISAM, and engine data - */ - if (!insert_table(key_length, key, block_table, - db_length, - tables_used->table->file->table_cache_type(), - 0, 0)) - DBUG_RETURN(0); - } - } -#endif + if (tables_used->table->file-> + register_query_cache_dependant_tables(thd, this, block_table, &n)) + DBUG_RETURN(0); } } DBUG_RETURN(n - counter); @@ -3295,12 +3321,14 @@ Query_cache::register_tables_from_list(TABLE_LIST *tables_used, SYNOPSIS register_all_tables() + thd Thread handle block Store tables in this block tables_used List if used tables tables_arg Not used ? */ -my_bool Query_cache::register_all_tables(Query_cache_block *block, +my_bool Query_cache::register_all_tables(THD *thd, + Query_cache_block *block, TABLE_LIST *tables_used, TABLE_COUNTER_TYPE tables_arg) { @@ -3311,7 +3339,7 @@ my_bool Query_cache::register_all_tables(Query_cache_block *block, Query_cache_block_table *block_table = block->table(0); - n= register_tables_from_list(tables_used, 0, block_table); + n= register_tables_from_list(thd, tables_used, 0, &block_table); if (n==0) { @@ -3320,6 +3348,8 @@ my_bool Query_cache::register_all_tables(Query_cache_block *block, tmp != block_table; tmp++) unlink_table(tmp); + if (block_table->parent) + unlink_table(block_table); } return test(n); } @@ -3338,7 +3368,8 @@ Query_cache::insert_table(uint key_len, char *key, Query_cache_block_table *node, uint32 db_length, uint8 cache_type, qc_engine_callback callback, - ulonglong engine_data) + ulonglong engine_data, + my_bool hash) { DBUG_ENTER("Query_cache::insert_table"); DBUG_PRINT("qcache", ("insert table node 0x%lx, len %d", @@ -3346,8 +3377,10 @@ Query_cache::insert_table(uint key_len, char *key, THD *thd= current_thd; - Query_cache_block *table_block= - (Query_cache_block *)hash_search(&tables, (uchar*) key, key_len); + Query_cache_block *table_block= + (hash ? + (Query_cache_block *) my_hash_search(&tables, (uchar*) key, key_len) : + NULL); if (table_block && table_block->table()->engine_data() != engine_data) @@ -3397,7 +3430,8 @@ Query_cache::insert_table(uint key_len, char *key, */ list_root->next= list_root->prev= list_root; - if (my_hash_insert(&tables, (const uchar *) table_block)) + if (hash && + my_hash_insert(&tables, (const uchar *) table_block)) { DBUG_PRINT("qcache", ("Can't insert table to hash")); // write_block_data return locked block @@ -3410,6 +3444,7 @@ Query_cache::insert_table(uint key_len, char *key, header->type(cache_type); header->callback(callback); header->engine_data(engine_data); + header->set_hashed(hash); /* We insert this table without the assumption that it isn't refrenenced by @@ -3463,7 +3498,9 @@ void Query_cache::unlink_table(Query_cache_block_table *node) Query_cache_block *table_block= neighbour->block(); double_linked_list_exclude(table_block, &tables_blocks); - hash_delete(&tables,(uchar *) table_block); + Query_cache_table *header= table_block->table(); + if (header->is_hashed()) + my_hash_delete(&tables,(uchar *) table_block); free_memory_block(table_block); } DBUG_VOID_RETURN; @@ -3936,6 +3973,9 @@ Query_cache::process_and_count_tables(THD *thd, TABLE_LIST *tables_used, table_alias_charset used here because it depends of lower_case_table_names variable */ + table_count+= tables_used->table->file-> + count_query_cache_dependant_tables(tables_type); + if (tables_used->table->s->tmp_table != NO_TMP_TABLE || (*tables_type & HA_CACHE_TBL_NOCACHE) || (tables_used->db_length == 5 && @@ -3948,18 +3988,6 @@ Query_cache::process_and_count_tables(THD *thd, TABLE_LIST *tables_used, "other non-cacheable table(s)")); DBUG_RETURN(0); } -#ifdef WITH_MYISAMMRG_STORAGE_ENGINE - /* - XXX FIXME: Some generic mechanism is required here instead of this - MYISAMMRG-specific implementation. - */ - if (tables_used->table->s->db_type()->db_type == DB_TYPE_MRG_MYISAM) - { - ha_myisammrg *handler = (ha_myisammrg *)tables_used->table->file; - MYRG_INFO *file = handler->myrg_info(); - table_count+= (file->end_table - file->open_tables); - } -#endif } } DBUG_RETURN(table_count); @@ -3967,6 +3995,18 @@ Query_cache::process_and_count_tables(THD *thd, TABLE_LIST *tables_used, /* +In non-embedded QC intercepts result in net_real_write +but if we have no net.vio then net_real_write +will not be called, so QC can't get results of the query +*/ +#ifdef EMBEDDED_LIBRARY +#define qc_is_able_to_intercept_result(T) 1 +#else +#define qc_is_able_to_intercept_result(T) ((T)->net.vio) +#endif + + +/* If query is cacheable return number tables in query (query without tables are not cached) */ @@ -3981,7 +4021,8 @@ Query_cache::is_cacheable(THD *thd, LEX *lex, if (thd->lex->safe_to_cache_query && (thd->variables.query_cache_type == 1 || (thd->variables.query_cache_type == 2 && (lex->select_lex.options & - OPTION_TO_QUERY_CACHE)))) + OPTION_TO_QUERY_CACHE))) && + qc_is_able_to_intercept_result(thd)) { DBUG_PRINT("qcache", ("options: %lx %lx type: %u", (long) OPTION_TO_QUERY_CACHE, @@ -3992,7 +4033,7 @@ Query_cache::is_cacheable(THD *thd, LEX *lex, tables_type))) DBUG_RETURN(0); - if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && + if (thd->in_multi_stmt_transaction_mode() && ((*tables_type)&HA_CACHE_TBL_TRANSACT)) { DBUG_PRINT("qcache", ("not in autocommin mode")); @@ -4003,11 +4044,12 @@ Query_cache::is_cacheable(THD *thd, LEX *lex, } DBUG_PRINT("qcache", - ("not interesting query: %d or not cacheable, options %lx %lx type: %u", + ("not interesting query: %d or not cacheable, options %lx %lx type: %u net->vio present: %u", (int) lex->sql_command, (long) OPTION_TO_QUERY_CACHE, (long) lex->select_lex.options, - (int) thd->variables.query_cache_type)); + (int) thd->variables.query_cache_type, + (uint) test(qc_is_able_to_intercept_result(thd)))); DBUG_RETURN(0); } @@ -4153,7 +4195,7 @@ my_bool Query_cache::move_by_type(uchar **border, uchar *key; size_t key_length; key=query_cache_table_get_key((uchar*) block, &key_length, 0); - hash_first(&tables, (uchar*) key, key_length, &record_idx); + my_hash_first(&tables, (uchar*) key, key_length, &record_idx); block->destroy(); new_block->init(len); @@ -4187,7 +4229,7 @@ my_bool Query_cache::move_by_type(uchar **border, /* Fix pointer to table name */ new_block->table()->table(new_block->table()->db() + tablename_offset); /* Fix hash to point at moved block */ - hash_replace(&tables, &record_idx, (uchar*) new_block); + my_hash_replace(&tables, &record_idx, (uchar*) new_block); DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx", len, (ulong) new_block, (ulong) *border)); @@ -4213,12 +4255,12 @@ my_bool Query_cache::move_by_type(uchar **border, uchar *key; size_t key_length; key=query_cache_query_get_key((uchar*) block, &key_length, 0); - hash_first(&queries, (uchar*) key, key_length, &record_idx); - // Move table of used tables - memmove((char*) new_block->table(0), (char*) block->table(0), - ALIGN_SIZE(n_tables*sizeof(Query_cache_block_table))); + my_hash_first(&queries, (uchar*) key, key_length, &record_idx); block->query()->unlock_n_destroy(); block->destroy(); + // Move table of used tables + memmove((char*) new_block->table(0), (char*) block->table(0), + ALIGN_SIZE(n_tables*sizeof(Query_cache_block_table))); new_block->init(len); new_block->type=Query_cache_block::QUERY; new_block->used=used; @@ -4269,19 +4311,19 @@ my_bool Query_cache::move_by_type(uchar **border, } while ( result_block != first_result_block ); } Query_cache_query *new_query= ((Query_cache_query *) new_block->data()); - my_rwlock_init(&new_query->lock, NULL); + mysql_rwlock_init(key_rwlock_query_cache_query_lock, &new_query->lock); /* If someone is writing to this block, inform the writer that the block has been moved. */ - NET *net = new_block->query()->writer(); - if (net != 0) + Query_cache_tls *query_cache_tls= new_block->query()->writer(); + if (query_cache_tls != NULL) { - net->query_cache_query= (uchar*) new_block; + query_cache_tls->first_query_block= new_block; } /* Fix hash to point at moved block */ - hash_replace(&queries, &record_idx, (uchar*) new_block); + my_hash_replace(&queries, &record_idx, (uchar*) new_block); DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx", len, (ulong) new_block, (ulong) *border)); break; @@ -4692,13 +4734,13 @@ my_bool Query_cache::check_integrity(bool locked) if (!locked) lock_and_suspend(); - if (hash_check(&queries)) + if (my_hash_check(&queries)) { DBUG_PRINT("error", ("queries hash is damaged")); result = 1; } - if (hash_check(&tables)) + if (my_hash_check(&tables)) { DBUG_PRINT("error", ("tables hash is damaged")); result = 1; @@ -4865,7 +4907,7 @@ my_bool Query_cache::check_integrity(bool locked) (ulong) block, (uint) block->type)); size_t length; uchar *key = query_cache_query_get_key((uchar*) block, &length, 0); - uchar* val = hash_search(&queries, key, length); + uchar* val = my_hash_search(&queries, key, length); if (((uchar*)block) != val) { DBUG_PRINT("error", ("block 0x%lx found in queries hash like 0x%lx", @@ -4900,7 +4942,7 @@ my_bool Query_cache::check_integrity(bool locked) (ulong) block, (uint) block->type)); size_t length; uchar *key = query_cache_table_get_key((uchar*) block, &length, 0); - uchar* val = hash_search(&tables, key, length); + uchar* val = my_hash_search(&tables, key, length); if (((uchar*)block) != val) { DBUG_PRINT("error", ("block 0x%lx found in tables hash like 0x%lx", |