Commit 877477f1 authored by unknown's avatar unknown
Browse files

BUG#21051: RESET QUERY CACHE very slow when query_cache_type=0

There were two problems: RESET QUERY CACHE took a long time to complete
and other threads were blocked during this time.

The patch does three things:
  1 fixes a bug with improper use of test-lock-test_again technique.
      AKA Double-Checked Locking is applicable here only in few places.
  2 Somewhat improves performance of RESET QUERY CACHE.
      Do my_hash_reset() instead of deleting elements one by one.  Note
      however that the slowdown also happens when inserting into sorted
      list of free blocks, should be rewritten using balanced tree.
  3 Makes RESET QUERY CACHE non-blocking.
      The patch adjusts the locking protocol of the query cache in the
      following way: it introduces a flag flush_in_progress, which is
      set when Query_cache::flush_cache() is in progress.  This call
      sets the flag on enter, and then releases the lock.  Every other
      call is able to acquire the lock, but does nothing if
      flush_in_progress is set (as if the query cache is disabled).
      The only exception is the concurrent calls to
      Query_cache::flush_cache(), that are blocked until the flush is
      over.  When leaving Query_cache::flush_cache(), the lock is
      acquired and the flag is reset, and one thread waiting on
      Query_cache::flush_cache() (if any) is notified that it may
      proceed.


include/mysql_com.h:
  Add comment for NET::query_cache_query.
sql/net_serv.cc:
  Use query_cache_init_query() for initialization of
  NET::query_cache_query if query cache is used.
  Do not access net->query_cache_query without a lock.
sql/sql_cache.cc:
  Fix bug with accessing query_cache_size, Query_cache_query::wri and
  thd->net.query_cache_query before acquiring the lock---leave
  double-check locking only in safe places.
  Wherever we check that cache is usable (query_cache_size > 0) we now
  also check that flush_in_progress is false, i.e. we are not in the
  middle of cache flush.
  Add Query_cache::not_in_flush_or_wait() method and use it in
  Query_cache::flush_cache(), so that threads doing cache flush will
  wait it to finish, while other threads will bypass the cache as if
  it is disabled.
  Extract Query_cache::free_query_internal() from Query_cache::free_query(),
  which does not removes elements from the hash, and use it together with
  my_hash_reset() in Query_cache::flush_cache().
sql/sql_cache.h:
  Add declarations for new members and methods.
  Make is_cacheable() a static method.
  Add query_cache_init_query() function.
sql/sql_class.cc:
  Use query_cache_init_query() for initialization of
  NET::query_cache_query.
parent d8180d44
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -210,7 +210,13 @@ typedef struct st_net {
  char last_error[MYSQL_ERRMSG_SIZE], sqlstate[SQLSTATE_LENGTH+1];
  unsigned int last_errno;
  unsigned char error;

  /*
    'query_cache_query' should be accessed only via query cache
    functions and methods to maintain proper locking.
  */
  gptr query_cache_query;

  my_bool report_error; /* We should report error (we have unreported error) */
  my_bool return_errno;
} NET;
+11 −6
Original line number Diff line number Diff line
@@ -96,8 +96,11 @@ extern uint test_flags;
extern ulong bytes_sent, bytes_received, net_big_packet_count;
extern pthread_mutex_t LOCK_bytes_sent , LOCK_bytes_received;
#ifndef MYSQL_INSTANCE_MANAGER
extern void query_cache_insert(NET *net, const char *packet, ulong length);
#ifdef HAVE_QUERY_CACHE
#define USE_QUERY_CACHE
extern void query_cache_init_query(NET *net);
extern void query_cache_insert(NET *net, const char *packet, ulong length);
#endif // HAVE_QUERY_CACHE
#define update_statistics(A) A
#endif /* MYSQL_INSTANCE_MANGER */
#endif /* defined(MYSQL_SERVER) && !defined(MYSQL_INSTANCE_MANAGER) */
@@ -133,7 +136,11 @@ my_bool my_net_init(NET *net, Vio* vio)
  net->compress=0; net->reading_or_writing=0;
  net->where_b = net->remain_in_buf=0;
  net->last_errno=0;
#ifdef USE_QUERY_CACHE
  query_cache_init_query(net);
#else
  net->query_cache_query= 0;
#endif
  net->report_error= 0;

  if (vio != 0)					/* If real connection */
@@ -552,9 +559,7 @@ net_real_write(NET *net,const char *packet,ulong len)
  my_bool net_blocking = vio_is_blocking(net->vio);
  DBUG_ENTER("net_real_write");

#if defined(MYSQL_SERVER) && defined(HAVE_QUERY_CACHE) \
                          && !defined(MYSQL_INSTANCE_MANAGER)
  if (net->query_cache_query != 0)
#if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE)
  query_cache_insert(net, packet, len);
#endif

+354 −228

File changed.

Preview size limit exceeded, changes collapsed.

+16 −1
Original line number Diff line number Diff line
@@ -195,7 +195,6 @@ extern "C"
  byte *query_cache_table_get_key(const byte *record, uint *length,
				  my_bool not_used);
}
void query_cache_insert(NET *thd, const char *packet, ulong length);
extern "C" void query_cache_invalidate_by_MyISAM_filename(const char* filename);


@@ -241,6 +240,12 @@ class Query_cache
  ulong free_memory, queries_in_cache, hits, inserts, refused,
    free_memory_blocks, total_blocks, lowmem_prunes;

private:
  pthread_cond_t COND_flush_finished;
  bool flush_in_progress;

  void free_query_internal(Query_cache_block *point);

protected:
  /*
    The following mutex is locked when searching or changing global
@@ -249,6 +254,12 @@ class Query_cache
    LOCK SEQUENCE (to prevent deadlocks):
      1. structure_guard_mutex
      2. query block (for operation inside query (query block/results))

    Thread doing cache flush releases the mutex once it sets
    flush_in_progress flag, so other threads may bypass the cache as
    if it is disabled, not waiting for reset to finish.  The exception
    is other threads that were going to do cache flush---they'll wait
    till the end of a flush operation.
  */
  pthread_mutex_t structure_guard_mutex;
  byte *cache;					// cache memory
@@ -358,6 +369,7 @@ class Query_cache
    If query is cacheable return number tables in query
    (query without tables not cached)
  */
  static
  TABLE_COUNTER_TYPE is_cacheable(THD *thd, uint32 query_len, char *query,
				  LEX *lex, TABLE_LIST *tables_used,
				  uint8 *tables_type);
@@ -410,6 +422,7 @@ class Query_cache

  void destroy();

  friend void query_cache_init_query(NET *net);
  friend void query_cache_insert(NET *net, const char *packet, ulong length);
  friend void query_cache_end_of_result(THD *thd);
  friend void query_cache_abort(NET *net);
@@ -435,6 +448,8 @@ class Query_cache

extern Query_cache query_cache;
extern TYPELIB query_cache_type_typelib;
void query_cache_init_query(NET *net);
void query_cache_insert(NET *net, const char *packet, ulong length);
void query_cache_end_of_result(THD *thd);
void query_cache_abort(NET *net);

+1 −1
Original line number Diff line number Diff line
@@ -223,7 +223,7 @@ THD::THD()
#endif
  client_capabilities= 0;                       // minimalistic client
  net.last_error[0]=0;                          // If error on boot
  net.query_cache_query=0;                      // If error on boot
  query_cache_init_query(&net);                 // If error on boot
  ull=0;
  system_thread= cleanup_done= abort_on_warning= no_warnings_for_error= 0;
  peer_port= 0;					// For SHOW PROCESSLIST