mirror of
https://github.com/MariaDB/server.git
synced 2025-01-17 20:42:30 +01:00
2a0d2fef51
cause ROLLBACK of statement", part 1. Review fixes. Do not send OK/EOF packets to the client until we reached the end of the current statement. This is a consolidation, to keep the functionality that is shared by all SQL statements in one place in the server. Currently this functionality includes: - close_thread_tables() - log_slow_statement(). After this patch and the subsequent patch for Bug#12713, it shall also include: - ha_autocommit_or_rollback() - net_end_statement() - query_cache_end_of_result(). In future it may also include: - mysql_reset_thd_for_next_command(). include/mysql_com.h: Rename now unused members of NET: no_send_ok, no_send_error, report_error. These were server-specific variables related to the client/server protocol. They have been made obsolete by this patch. Previously the same members of NET were used to store the error message both on the client and on the server. The error message was stored in net.last_error (client: mysql->net.last_error, server: thd->net.last_error). The error code was stored in net.last_errno (client: mysql->net.last_errno, server: thd->net.last_errno). The server error code and message are now stored elsewhere (in the Diagnostics_area), thus NET members are no longer used by the server. Rename last_error to client_last_error, last_errno to client_last_errno to avoid potential bugs introduced by merges. include/mysql_h.ic: Update the ABI file to reflect a rename. Renames do not break the binary compatibility. libmysql/libmysql.c: Rename last_error to client_last_error, last_errno to client_last_errno. This is necessary to ensure no unnoticed bugs introduced by merged changesets. Remove net.report_error, net.no_send_ok, net.no_send_error. libmysql/manager.c: Rename net.last_errno to net.client_last_errno. libmysqld/lib_sql.cc: Rename net.last_errno to net.client_last_errno. Update the embedded implementation of the client-server protocol to reflect the refactoring of protocol.cc. libmysqld/libmysqld.c: Rename net.last_errno to net.client_last_errno. mysql-test/r/events.result: Update to reflect the change in mysql_rm_db(). Now we drop stored routines and events for a given database name only if there is a directory for this database name. ha_drop_database() and query_cache_invalidate() are called likewise. Previously we would attempt to drop routines/events even if database directory was not found (it worked, since routines and events are stored in tables). This fixes Bug 29958 "Weird message on DROP DATABASE if mysql.proc does not exist". The change was done because the previous code used to call send_ok() twice, which led to an assertion failure when asserts against it were added by this patch. mysql-test/r/grant.result: Fix the patch for Bug 16470, now FLUSH PRIVILEGES produces an error if mysql.procs_priv is missing. This fixes the assert that send_ok() must not called after send_error() (the original patch for Bug 16470 was prone to this). mysql-test/suite/rpl/r/rpl_row_tabledefs_2myisam.result: Produce a more detailed error message. mysql-test/suite/rpl/r/rpl_row_tabledefs_3innodb.result: Produce a more detailed error message. mysql-test/t/grant.test: Update the test, now FLUSH PRIVILEGES returns an error if mysql.procs_priv is missing. server-tools/instance-manager/mysql_connection.cc: Rename net.last_errno to net.client_last_errno. sql/ha_ndbcluster_binlog.cc: Add asserts. Use getters to access statement status information. Add a comment why run_query() is broken. Reset the diagnostics area in the end of run_query() to fulfill the invariant that the diagnostics_area is never assigned twice per statement (see the comment in the code when this can happen). We still do not clear thd->is_fatal_error and thd->is_slave_error, which may lead to bugs, I consider the whole affair as something to be dealt with separately. sql/ha_partition.cc: fatal_error() doesn't set an error by itself. Perhaps we should remove this method altogether and instead add a flag to my_error to set thd->is_fatal_error property. Meanwhile, this change is a part of inspection made to the entire source code with the goal to ensure that fatal_error() is always accompanied by my_error(). sql/item_func.cc: There is no net.last_error anymore. Remove the obsolete assignment. sql/log_event.cc: Use getters to access statement error status information. sql/log_event_old.cc: Use getters to access statement error status information. sql/mysqld.cc: Previously, if a continue handler for an error was found, my_message_sql() would not set an error in THD. Since the current statement must be aborted in any case, find_handler() had a hack to assign thd->net.report_error to 1. Remove this hack. Set an error in my_message_sql() even if the continue handler is found. The error will be cleared anyway when the handler is executed. This is one action among many in this patch to ensure the invariant that whenever thd->is_error() is TRUE, we have a message in thd->main_da.message(). sql/net_serv.cc: Use a full-blown my_error() in net_serv.cc to report an error, instead of just setting net->last_errno. This ensures the invariant that whenever thd->is_error() returns TRUE, we have a message in thd->main_da.message(). Remove initialization of removed NET members. sql/opt_range.cc: Use my_error() instead of just raising thd->net.report_error. This ensures the invariant that whenever thd->is_error() returns TRUE, there is a message in thd->main_da.message(). sql/opt_sum.cc: Move invocation of fatal_error() right next to the place where we set the error message. That makes it easier to track that whenever fatal_error() is called, there is a message in THD. sql/protocol.cc: Rename send_ok() and send_eof() to net_send_ok() and net_send_eof() respectively. These functions write directly to the network and are not for use anywhere outside the client/server protocol code. Remove the code that was responsible for cases when either there is no error code, or no error message, or both. Instead the calling code ensures that they are always present. Asserts are added to enforce the invariant. Instead of a direct access to thd->server_status and thd->total_warn_count use function parameters, since these from now on don't always come directly from THD. Introduce net_end_statement(), the single-entry-point replacement API for send_ok(), send_eof() and net_send_error(). Implement Protocol::end_partial_result_set to use in select_send::abort() when there is a continue handler. sql/protocol.h: Update declarations. sql/repl_failsafe.cc: Use getters to access statement status information in THD. Rename net.last_error to net.client_last_error. sql/rpl_record.cc: Set an error message in prepare_record() if there is no default value for the field -- later we do print this message to the client. sql/rpl_rli.cc: Use getters to access statement status information in THD. sql/slave.cc: In create_table_from_dump() (a common function that is used in LOAD MASTER TABLE SQL statement and COM_LOAD_MASTER_DATA), instead of hacks with no_send_ok, clear the diagnostics area when mysql_rm_table() succeeded. Update has_temporary_error() to work correctly when no error is set. This is the case when Incident_log_event is executed: it always returns an error but does not set an error message. Use getters to access error status information. sql/sp_head.cc: Instead of hacks with no_send_error, work through the diagnostics area interface to suppress sending of OK/ERROR packets to the client. Move query_cache_end_of_result before log_slow_statement(), similarly to how it's done in dispatch_command(). sql/sp_rcontext.cc: Remove hacks with assignment of thd->net.report_error, they are not necessary any more (see the changes in mysqld.cc). sql/sql_acl.cc: Use getters to access error status information in THD. sql/sql_base.cc: Access thd->main_da.sql_errno() only if there is an error. This fixes a bug when auto-discovery, that was effectively disabled under pre-locking. sql/sql_binlog.cc: Remove hacks with no_send_ok/no_send_error, they are not necessary anymore: the caller is responsible for network communication. sql/sql_cache.cc: Disable sending of OK/ERROR/EOF packet in the end of dispatch_command if the response has been served from the query cache. This raises the question whether we should store EOF packet in the query cache at all, or generate it anew for each statement (we should generate it anew), but this is to be addressed separately. sql/sql_class.cc: Implement class Diagnostics_area. Please see comments in sql_class.h for details. Fix a subtle coding mistake in select_send::send_data: when on slave, an error in Item::send() was ignored. The problem became visible due to asserts that the diagnostics area is never double assigned. Remove initialization of removed NET members. In select_send::abort() do not call select_send::send_eof(). This is not inheritance-safe. Even if a stored procedure continue handler is found, the current statement is aborted, not succeeded. Instead introduce a Protocol API to send the required response, Protocol::end_partial_result_set(). This simplifies implementation of select_send::send_eof(). No need to add more asserts that there is no error, there is an assert inside Diagnostics_area::set_ok_status() already. Leave no trace of no_send_* in the code. sql/sql_class.h: Declare class Diagnostics_area. Remove the hack with no_send_ok from Substatement_state. Provide inline implementations of send_ok/send_eof. Add commetns. sql/sql_connect.cc: Remove hacks with no_send_error. Since now an error in THD is always set if net->error, it's not necessary to check both net->error and thd->is_error() in the do_command loop. Use thd->main_da.message() instead of net->last_errno. Remove the hack with is_slave_error in sys_init_connect. Since now we do not reset the diagnostics area in net_send_error (it's reset at the beginning of the next statement), we can access it safely even after execute_init_command. sql/sql_db.cc: Update the code to satisfy the invariant that the diagnostics area is never assigned twice. Incidentally, this fixes Bug 29958 "Weird message on DROP DATABASE if mysql.proc does not exist". sql/sql_delete.cc: Change multi-delete to abort in abort(), as per select_send protocol. Fixes the merge error with the test for Bug 29136 sql/sql_derived.cc: Use getters to access error information. sql/sql_insert.cc: Use getters to access error information. sql-common/client.c: Rename last_error to client_last_error, last_errno to client_last_errno. sql/sql_parse.cc: Remove hacks with no_send_error. Deploy net_end_statement(). The story of COM_SHUTDOWN is interesting. Long story short, the server would become on its death's door, and only no_send_ok/no_send_error assigned by send_ok()/net_send_error() would hide its babbling from the client. First of all, COM_QUIT does not require a response. So, the comment saying "Let's send a response to possible COM_QUIT" is not only groundless (even mysqladmin shutdown/mysql_shutdown() doesn't send COM_QUIT after COM_SHUTDOWN), it's plainly incorrect. Secondly, besides this additional 'OK' packet to respond to a hypothetical COM_QUIT, there was the following code in dispatch_command(): if (thd->killed) thd->send_kill_message(); if (thd->is_error() net_send_error(thd); This worked out really funny for the thread through which COM_SHUTDOWN was delivered: we would get COM_SHUTDOWN, say okay, say okay again, kill everybody, get the kill signal ourselves, and then attempt to say "Server shutdown in progress" to the client that is very likely long gone. This all became visible when asserts were added that the Diagnostics_area is not assigned twice. Move query_cache_end_of_result() to the end of dispatch_command(), since net_send_eof() has been moved there. This is safe, query_cache_end_of_result() is a no-op if there is no started query in the cache. Consistently use select_send interface to call abort() or send_eof() depending on the operation result. Remove thd->fatal_error() from reset_master(), it was a no-op. in hacks with no_send_error woudl save us from complete breakage of the client/server protocol. Consistently use select_send::abort() whenever there is an error, and select_send::send_eof() in case of success. The issue became visible due to added asserts. sql/sql_partition.cc: Always set an error in THD whenever there is a call to fatal_error(). sql/sql_prepare.cc: Deploy class Diagnostics_area. Remove the unnecessary juggling with the protocol in Select_fetch_protocol_binary::send_eof(). EOF packet format is protocol-independent. sql/sql_select.cc: Call fatal_error() directly in opt_sum_query. Call my_error() whenever we call thd->fatal_error(). sql/sql_servers.cc: Use getters to access error information in THD. sql/sql_show.cc: Use getters to access error information in THD. Add comments. Call my_error() whenever we call fatal_error(). sql/sql_table.cc: Replace hacks with no_send_ok with the interface of the diagnostics area. Clear the error if ENOENT error in ha_delete_table(). sql/sql_update.cc: Introduce multi_update::abort(), which is the proper way to abort a multi-update. This fixes the merge conflict between this patch and the patch for Bug 29136. sql/table.cc: Use a getter to access error information in THD. sql/tztime.cc: Use a getter to access error information in THD.
4498 lines
139 KiB
C++
4498 lines
139 KiB
C++
/* Copyright (C) 2000 MySQL 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
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|
|
|
/*
|
|
Description of the query cache:
|
|
|
|
1. Query_cache object consists of
|
|
- query cache memory pool (cache)
|
|
- queries hash (queries)
|
|
- tables hash (tables)
|
|
- list of blocks ordered as they allocated in memory
|
|
(first_block)
|
|
- list of queries block (queries_blocks)
|
|
- list of used tables (tables_blocks)
|
|
|
|
2. Query cache memory pool (cache) consists of
|
|
- table of steps of memory bins allocation
|
|
- table of free memory bins
|
|
- blocks of memory
|
|
|
|
3. Memory blocks
|
|
|
|
Every memory block has the following structure:
|
|
|
|
+----------------------------------------------------------+
|
|
| Block header (Query_cache_block structure) |
|
|
+----------------------------------------------------------+
|
|
|Table of database table lists (used for queries & tables) |
|
|
+----------------------------------------------------------+
|
|
| Type depended header |
|
|
|(Query_cache_query, Query_cache_table, Query_cache_result)|
|
|
+----------------------------------------------------------+
|
|
| Data ... |
|
|
+----------------------------------------------------------+
|
|
|
|
Block header consists of:
|
|
- type:
|
|
FREE Free memory block
|
|
QUERY Query block
|
|
RESULT Ready to send result
|
|
RES_CONT Result's continuation
|
|
RES_BEG First block of results, that is not yet complete,
|
|
written to cache
|
|
RES_INCOMPLETE Allocated for results data block
|
|
TABLE Block with database table description
|
|
INCOMPLETE The destroyed block
|
|
- length of block (length)
|
|
- length of data & headers (used)
|
|
- physical list links (pnext/pprev) - used for the list of
|
|
blocks ordered as they are allocated in physical memory
|
|
- logical list links (next/prev) - used for queries block list, tables block
|
|
list, free memory block lists and list of results block in query
|
|
- number of elements in table of database table list (n_tables)
|
|
|
|
4. Query & results blocks
|
|
|
|
Query stored in cache consists of following blocks:
|
|
|
|
more more
|
|
recent+-------------+ old
|
|
<-----|Query block 1|------> double linked list of queries block
|
|
prev | | next
|
|
+-------------+
|
|
<-| table 0 |-> (see "Table of database table lists" description)
|
|
<-| table 1 |->
|
|
| ... | +--------------------------+
|
|
+-------------+ +-------------------------+ |
|
|
NET | | | V V |
|
|
struct| | +-+------------+ +------------+ |
|
|
<-----|query header |----->|Result block|-->|Result block|-+ doublelinked
|
|
writer| |result| |<--| | list of results
|
|
+-------------+ +------------+ +------------+
|
|
|charset | +------------+ +------------+ no table of dbtables
|
|
|encoding + | | result | | result |
|
|
|query text |<-----| header | | header |------+
|
|
+-------------+parent| | | |parent|
|
|
^ +------------+ +------------+ |
|
|
| |result data | |result data | |
|
|
| +------------+ +------------+ |
|
|
+---------------------------------------------------+
|
|
|
|
First query is registered. During the registration query block is
|
|
allocated. This query block is included in query hash and is linked
|
|
with appropriate database tables lists (if there is no appropriate
|
|
list exists it will be created).
|
|
|
|
Later when query has performed results is written into the result blocks.
|
|
A result block cannot be smaller then QUERY_CACHE_MIN_RESULT_DATA_SIZE.
|
|
|
|
When new result is written to cache it is appended to the last result
|
|
block, if no more free space left in the last block, new block is
|
|
allocated.
|
|
|
|
5. Table of database table lists.
|
|
|
|
For quick invalidation of queries all query are linked in lists on used
|
|
database tables basis (when table will be changed (insert/delete/...)
|
|
this queries will be removed from cache).
|
|
|
|
Root of such list is table block:
|
|
|
|
+------------+ list of used tables (used while invalidation of
|
|
<----| Table |-----> whole database)
|
|
prev| block |next +-----------+
|
|
| | +-----------+ |Query block|
|
|
| | |Query block| +-----------+
|
|
+------------+ +-----------+ | ... |
|
|
+->| table 0 |------>|table 0 |----->| table N |---+
|
|
|+-| |<------| |<-----| |<-+|
|
|
|| +------------+ | ... | | ... | ||
|
|
|| |table header| +-----------+ +-----------+ ||
|
|
|| +------------+ | ... | | ... | ||
|
|
|| |db name + | +-----------+ +-----------+ ||
|
|
|| |table name | ||
|
|
|| +------------+ ||
|
|
|+--------------------------------------------------------+|
|
|
+----------------------------------------------------------+
|
|
|
|
Table block is included into the tables hash (tables).
|
|
|
|
6. Free blocks, free blocks bins & steps of freeblock bins.
|
|
|
|
When we just started only one free memory block existed. All query
|
|
cache memory (that will be used for block allocation) were
|
|
containing in this block.
|
|
When a new block is allocated we find most suitable memory block
|
|
(minimal of >= required size). If such a block can not be found, we try
|
|
to find max block < required size (if we allocate block for results).
|
|
If there is no free memory, oldest query is removed from cache, and then
|
|
we try to allocate memory. Last step should be repeated until we find
|
|
suitable block or until there is no unlocked query found.
|
|
|
|
If the block is found and its length more then we need, it should be
|
|
split into 2 blocks.
|
|
New blocks cannot be smaller then min_allocation_unit_bytes.
|
|
|
|
When a block becomes free, its neighbor-blocks should be tested and if
|
|
there are free blocks among them, they should be joined into one block.
|
|
|
|
Free memory blocks are stored in bins according to their sizes.
|
|
The bins are stored in size-descending order.
|
|
These bins are distributed (by size) approximately logarithmically.
|
|
|
|
First bin (number 0) stores free blocks with
|
|
size <= query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2.
|
|
It is first (number 0) step.
|
|
On the next step distributed (1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
|
|
QUERY_CACHE_MEM_BIN_PARTS_MUL bins. This bins allocated in interval from
|
|
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 to
|
|
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 >>
|
|
QUERY_CACHE_MEM_BIN_STEP_PWR2
|
|
...
|
|
On each step interval decreases in 2 power of
|
|
QUERY_CACHE_MEM_BIN_STEP_PWR2
|
|
times, number of bins (that distributed on this step) increases. If on
|
|
the previous step there were N bins distributed , on the current there
|
|
would be distributed
|
|
(N + QUERY_CACHE_MEM_BIN_PARTS_INC) * QUERY_CACHE_MEM_BIN_PARTS_MUL
|
|
bins.
|
|
Last distributed bin stores blocks with size near min_allocation_unit
|
|
bytes.
|
|
|
|
For example:
|
|
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 = 100,
|
|
min_allocation_unit = 17,
|
|
QUERY_CACHE_MEM_BIN_STEP_PWR2 = 1,
|
|
QUERY_CACHE_MEM_BIN_PARTS_INC = 1,
|
|
QUERY_CACHE_MEM_BIN_PARTS_MUL = 1
|
|
(in followed picture showed right (low) bound of bin):
|
|
|
|
| 100>>1 50>>1 |25>>1|
|
|
| | | | | |
|
|
| 100 75 50 41 33 25 21 18 15| 12 | - bins right (low) bounds
|
|
|
|
|\---/\-----/\--------/\--------|---/ |
|
|
| 0 1 2 3 | | - steps
|
|
\-----------------------------/ \---/
|
|
bins that we store in cache this bin showed for example only
|
|
|
|
|
|
Calculation of steps/bins distribution is performed only when query cache
|
|
is resized.
|
|
|
|
When we need to find appropriate bin, first we should find appropriate
|
|
step, then we should calculate number of bins that are using data
|
|
stored in Query_cache_memory_bin_step structure.
|
|
|
|
Free memory blocks are sorted in bins in lists with size-ascending order
|
|
(more small blocks needed frequently then bigger one).
|
|
|
|
7. Packing cache.
|
|
|
|
Query cache packing is divided into two operation:
|
|
- pack_cache
|
|
- join_results
|
|
|
|
pack_cache moved all blocks to "top" of cache and create one block of free
|
|
space at the "bottom":
|
|
|
|
before pack_cache after pack_cache
|
|
+-------------+ +-------------+
|
|
| query 1 | | query 1 |
|
|
+-------------+ +-------------+
|
|
| table 1 | | table 1 |
|
|
+-------------+ +-------------+
|
|
| results 1.1 | | results 1.1 |
|
|
+-------------+ +-------------+
|
|
| free | | query 2 |
|
|
+-------------+ +-------------+
|
|
| query 2 | | table 2 |
|
|
+-------------+ ---> +-------------+
|
|
| table 2 | | results 1.2 |
|
|
+-------------+ +-------------+
|
|
| results 1.2 | | results 2 |
|
|
+-------------+ +-------------+
|
|
| free | | free |
|
|
+-------------+ | |
|
|
| results 2 | | |
|
|
+-------------+ | |
|
|
| free | | |
|
|
+-------------+ +-------------+
|
|
|
|
pack_cache scan blocks in physical address order and move every non-free
|
|
block "higher".
|
|
|
|
pack_cach remove every free block it finds. The length of the deleted block
|
|
is accumulated to the "gap". All non free blocks should be shifted with the
|
|
"gap" step.
|
|
|
|
join_results scans all complete queries. If the results of query are not
|
|
stored in the same block, join_results tries to move results so, that they
|
|
are stored in one block.
|
|
|
|
before join_results after join_results
|
|
+-------------+ +-------------+
|
|
| query 1 | | query 1 |
|
|
+-------------+ +-------------+
|
|
| table 1 | | table 1 |
|
|
+-------------+ +-------------+
|
|
| results 1.1 | | free |
|
|
+-------------+ +-------------+
|
|
| query 2 | | query 2 |
|
|
+-------------+ +-------------+
|
|
| table 2 | | table 2 |
|
|
+-------------+ ---> +-------------+
|
|
| results 1.2 | | free |
|
|
+-------------+ +-------------+
|
|
| results 2 | | results 2 |
|
|
+-------------+ +-------------+
|
|
| free | | results 1 |
|
|
| | | |
|
|
| | +-------------+
|
|
| | | free |
|
|
| | | |
|
|
+-------------+ +-------------+
|
|
|
|
If join_results allocated new block(s) then we need call pack_cache again.
|
|
|
|
7. Interface
|
|
The query cache interfaces with the rest of the server code through 7
|
|
functions:
|
|
1. Query_cache::send_result_to_client
|
|
- Called before parsing and used to match a statement with the stored
|
|
queries hash.
|
|
If a match is found the cached result set is sent through repeated
|
|
calls to net_real_write. (note: calling thread doesn't have a regis-
|
|
tered result set writer: thd->net.query_cache_query=0)
|
|
2. Query_cache::store_query
|
|
- Called just before handle_select() and is used to register a result
|
|
set writer to the statement currently being processed
|
|
(thd->net.query_cache_query).
|
|
3. query_cache_insert
|
|
- Called from net_real_write to append a result set to a cached query
|
|
if (and only if) this query has a registered result set writer
|
|
(thd->net.query_cache_query).
|
|
4. Query_cache::invalidate
|
|
- Called from various places to invalidate query cache based on data-
|
|
base, table and myisam file name. During an on going invalidation
|
|
the query cache is temporarily disabled.
|
|
5. Query_cache::flush
|
|
- Used when a RESET QUERY CACHE is issued. This clears the entire
|
|
cache block by block.
|
|
6. Query_cache::resize
|
|
- Used to change the available memory used by the query cache. This
|
|
will also invalidate the entrie query cache in one free operation.
|
|
7. Query_cache::pack
|
|
- Used when a FLUSH QUERY CACHE is issued. This changes the order of
|
|
the used memory blocks in physical memory order and move all avail-
|
|
able memory to the 'bottom' of the memory.
|
|
|
|
|
|
TODO list:
|
|
|
|
- Delayed till after-parsing qache answer (for column rights processing)
|
|
- Optimize cache resizing
|
|
- if new_size < old_size then pack & shrink
|
|
- if new_size > old_size copy cached query to new cache
|
|
- Move MRG_MYISAM table type processing to handlers, something like:
|
|
tables_used->table->file->register_used_filenames(callback,
|
|
first_argument);
|
|
- QC improvement suggested by Monty:
|
|
- Add a counter in open_table() for how many MERGE (ISAM or MyISAM)
|
|
tables are cached in the table cache.
|
|
(This will be trivial when we have the new table cache in place I
|
|
have been working on)
|
|
- After this we can add the following test around the for loop in
|
|
is_cacheable::
|
|
|
|
if (thd->temp_tables || global_merge_table_count)
|
|
|
|
- Another option would be to set thd->lex->safe_to_cache_query to 0
|
|
in 'get_lock_data' if any of the tables was a tmp table or a
|
|
MRG_ISAM table.
|
|
(This could be done with almost no speed penalty)
|
|
*/
|
|
|
|
#include "mysql_priv.h"
|
|
#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"
|
|
|
|
#ifdef EMBEDDED_LIBRARY
|
|
#include "emb_qcache.h"
|
|
#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")); \
|
|
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")); \
|
|
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")); \
|
|
else DBUG_PRINT("lock", ("rwlock unlock FAILED %d", errno)); }
|
|
#define STRUCT_LOCK(M) {DBUG_PRINT("lock", ("%d struct lock...",__LINE__)); \
|
|
pthread_mutex_lock(M);DBUG_PRINT("lock", ("struct lock OK"));}
|
|
#define STRUCT_UNLOCK(M) { \
|
|
DBUG_PRINT("lock", ("%d struct unlock...",__LINE__)); \
|
|
pthread_mutex_unlock(M);DBUG_PRINT("lock", ("struct unlock OK"));}
|
|
#define BLOCK_LOCK_WR(B) {DBUG_PRINT("lock", ("%d LOCK_WR 0x%lx",\
|
|
__LINE__,(ulong)(B))); \
|
|
B->query()->lock_writing();}
|
|
#define BLOCK_LOCK_RD(B) {DBUG_PRINT("lock", ("%d LOCK_RD 0x%lx",\
|
|
__LINE__,(ulong)(B))); \
|
|
B->query()->lock_reading();}
|
|
#define BLOCK_UNLOCK_WR(B) { \
|
|
DBUG_PRINT("lock", ("%d UNLOCK_WR 0x%lx",\
|
|
__LINE__,(ulong)(B)));B->query()->unlock_writing();}
|
|
#define BLOCK_UNLOCK_RD(B) { \
|
|
DBUG_PRINT("lock", ("%d UNLOCK_RD 0x%lx",\
|
|
__LINE__,(ulong)(B)));B->query()->unlock_reading();}
|
|
#define DUMP(C) DBUG_EXECUTE("qcache", {\
|
|
(C)->cache_dump(); (C)->queries_dump();(C)->tables_dump();})
|
|
#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 STRUCT_LOCK(M) pthread_mutex_lock(M)
|
|
#define STRUCT_UNLOCK(M) pthread_mutex_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()
|
|
#define BLOCK_UNLOCK_RD(B) B->query()->unlock_reading()
|
|
#define DUMP(C)
|
|
#endif
|
|
|
|
const char *query_cache_type_names[]= { "OFF", "ON", "DEMAND",NullS };
|
|
TYPELIB query_cache_type_typelib=
|
|
{
|
|
array_elements(query_cache_type_names)-1,"", query_cache_type_names, NULL
|
|
};
|
|
|
|
/*****************************************************************************
|
|
Query_cache_block_table method(s)
|
|
*****************************************************************************/
|
|
|
|
inline Query_cache_block * Query_cache_block_table::block()
|
|
{
|
|
return (Query_cache_block *)(((uchar*)this) -
|
|
ALIGN_SIZE(sizeof(Query_cache_block_table)*n) -
|
|
ALIGN_SIZE(sizeof(Query_cache_block)));
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Query_cache_block method(s)
|
|
*****************************************************************************/
|
|
|
|
void Query_cache_block::init(ulong block_length)
|
|
{
|
|
DBUG_ENTER("Query_cache_block::init");
|
|
DBUG_PRINT("qcache", ("init block: 0x%lx length: %lu", (ulong) this,
|
|
block_length));
|
|
length = block_length;
|
|
used = 0;
|
|
type = Query_cache_block::FREE;
|
|
n_tables = 0;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void Query_cache_block::destroy()
|
|
{
|
|
DBUG_ENTER("Query_cache_block::destroy");
|
|
DBUG_PRINT("qcache", ("destroy block 0x%lx, type %d",
|
|
(ulong) this, type));
|
|
type = INCOMPLETE;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
inline 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)
|
|
{
|
|
return (uchar*)( ((uchar*)this) + headers_len() );
|
|
}
|
|
|
|
inline Query_cache_query * Query_cache_block::query()
|
|
{
|
|
#ifndef DBUG_OFF
|
|
if (type != QUERY)
|
|
query_cache.wreck(__LINE__, "incorrect block type");
|
|
#endif
|
|
return (Query_cache_query *) data();
|
|
}
|
|
|
|
inline Query_cache_table * Query_cache_block::table()
|
|
{
|
|
#ifndef DBUG_OFF
|
|
if (type != TABLE)
|
|
query_cache.wreck(__LINE__, "incorrect block type");
|
|
#endif
|
|
return (Query_cache_table *) data();
|
|
}
|
|
|
|
inline Query_cache_result * Query_cache_block::result()
|
|
{
|
|
#ifndef DBUG_OFF
|
|
if (type != RESULT && type != RES_CONT && type != RES_BEG &&
|
|
type != RES_INCOMPLETE)
|
|
query_cache.wreck(__LINE__, "incorrect block type");
|
|
#endif
|
|
return (Query_cache_result *) data();
|
|
}
|
|
|
|
inline 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)) +
|
|
n*sizeof(Query_cache_block_table)));
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* Query_cache_table method(s)
|
|
*****************************************************************************/
|
|
|
|
extern "C"
|
|
{
|
|
uchar *query_cache_table_get_key(const uchar *record, size_t *length,
|
|
my_bool not_used __attribute__((unused)))
|
|
{
|
|
Query_cache_block* table_block = (Query_cache_block*) record;
|
|
*length = (table_block->used - table_block->headers_len() -
|
|
ALIGN_SIZE(sizeof(Query_cache_table)));
|
|
return (((uchar *) table_block->data()) +
|
|
ALIGN_SIZE(sizeof(Query_cache_table)));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Query_cache_query methods
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
Following methods work for block read/write locking only in this
|
|
particular case and in interaction with structure_guard_mutex.
|
|
|
|
Lock for write prevents any other locking. (exclusive use)
|
|
Lock for read prevents only locking for write.
|
|
*/
|
|
|
|
inline void Query_cache_query::lock_writing()
|
|
{
|
|
RW_WLOCK(&lock);
|
|
}
|
|
|
|
|
|
/*
|
|
Needed for finding queries, that we may delete from cache.
|
|
We don't want to wait while block become unlocked. In addition,
|
|
block locking means that query is now used and we don't need to
|
|
remove it.
|
|
*/
|
|
|
|
my_bool Query_cache_query::try_lock_writing()
|
|
{
|
|
DBUG_ENTER("Query_cache_block::try_lock_writing");
|
|
if (rw_trywrlock(&lock)!=0)
|
|
{
|
|
DBUG_PRINT("info", ("can't lock rwlock"));
|
|
DBUG_RETURN(0);
|
|
}
|
|
DBUG_PRINT("info", ("rwlock 0x%lx locked", (ulong) &lock));
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
|
|
inline void Query_cache_query::lock_reading()
|
|
{
|
|
RW_RLOCK(&lock);
|
|
}
|
|
|
|
|
|
inline void Query_cache_query::unlock_writing()
|
|
{
|
|
RW_UNLOCK(&lock);
|
|
}
|
|
|
|
|
|
inline void Query_cache_query::unlock_reading()
|
|
{
|
|
RW_UNLOCK(&lock);
|
|
}
|
|
|
|
|
|
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);
|
|
lock_writing();
|
|
DBUG_PRINT("qcache", ("inited & locked query for block 0x%lx",
|
|
(long) (((uchar*) this) -
|
|
ALIGN_SIZE(sizeof(Query_cache_block)))));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache_query::unlock_n_destroy()
|
|
{
|
|
DBUG_ENTER("Query_cache_query::unlock_n_destroy");
|
|
DBUG_PRINT("qcache", ("destroyed & unlocked query for block 0x%lx",
|
|
(long) (((uchar*) this) -
|
|
ALIGN_SIZE(sizeof(Query_cache_block)))));
|
|
/*
|
|
The following call is not needed on system where one can destroy an
|
|
active semaphore
|
|
*/
|
|
this->unlock_writing();
|
|
rwlock_destroy(&lock);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
extern "C"
|
|
{
|
|
uchar *query_cache_query_get_key(const uchar *record, size_t *length,
|
|
my_bool not_used)
|
|
{
|
|
Query_cache_block *query_block = (Query_cache_block*) record;
|
|
*length = (query_block->used - query_block->headers_len() -
|
|
ALIGN_SIZE(sizeof(Query_cache_query)));
|
|
return (((uchar *) query_block->data()) +
|
|
ALIGN_SIZE(sizeof(Query_cache_query)));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Functions to store things into the query cache
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
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.
|
|
|
|
This means that if we see 'NET::query_cache_query == 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
|
|
thread always sees results of its memory operations, mutex or not).
|
|
If later we see 'NET::query_cache_query == 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
|
|
this query again.
|
|
|
|
See also comments in Query_cache::store_query() and
|
|
Query_cache::send_result_to_client().
|
|
|
|
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.
|
|
*/
|
|
|
|
|
|
void query_cache_init_query(NET *net)
|
|
{
|
|
/*
|
|
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.
|
|
*/
|
|
net->query_cache_query= 0;
|
|
}
|
|
|
|
|
|
/*
|
|
Insert the packet into the query cache.
|
|
*/
|
|
|
|
void query_cache_insert(NET *net, const char *packet, ulong length)
|
|
{
|
|
DBUG_ENTER("query_cache_insert");
|
|
|
|
/* See the comment on double-check locking usage above. */
|
|
if (net->query_cache_query == 0)
|
|
DBUG_VOID_RETURN;
|
|
|
|
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
|
bool interrupt;
|
|
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
|
|
if (interrupt)
|
|
{
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
return;
|
|
}
|
|
|
|
Query_cache_block *query_block= (Query_cache_block*)net->query_cache_query;
|
|
if (!query_block)
|
|
{
|
|
/*
|
|
We lost the writer and the currently processed query has been
|
|
invalidated; there is nothing left to do.
|
|
*/
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
Query_cache_query *header= query_block->query();
|
|
Query_cache_block *result= header->result();
|
|
|
|
DUMP(&query_cache);
|
|
BLOCK_LOCK_WR(query_block);
|
|
DBUG_PRINT("qcache", ("insert packet %lu bytes long",length));
|
|
|
|
/*
|
|
On success, STRUCT_UNLOCK is done by append_result_data. Otherwise, we
|
|
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))
|
|
{
|
|
DBUG_PRINT("warning", ("Can't append data"));
|
|
header->result(result);
|
|
DBUG_PRINT("qcache", ("free query 0x%lx", (ulong) query_block));
|
|
// The following call will remove the lock on query_block
|
|
query_cache.free_query(query_block);
|
|
// append_result_data no success => we need unlock
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
header->result(result);
|
|
header->last_pkt_nr= net->pkt_nr;
|
|
BLOCK_UNLOCK_WR(query_block);
|
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0););
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void query_cache_abort(NET *net)
|
|
{
|
|
DBUG_ENTER("query_cache_abort");
|
|
|
|
/* See the comment on double-check locking usage above. */
|
|
if (net->query_cache_query == 0)
|
|
DBUG_VOID_RETURN;
|
|
|
|
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
|
bool interrupt;
|
|
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
|
|
if (interrupt)
|
|
{
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
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);
|
|
if (query_block)
|
|
{
|
|
DUMP(&query_cache);
|
|
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););
|
|
}
|
|
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void query_cache_end_of_result(THD *thd)
|
|
{
|
|
Query_cache_block *query_block;
|
|
DBUG_ENTER("query_cache_end_of_result");
|
|
|
|
/* See the comment on double-check locking usage above. */
|
|
if (thd->net.query_cache_query == 0)
|
|
DBUG_VOID_RETURN;
|
|
|
|
if (thd->killed)
|
|
{
|
|
query_cache_abort(&thd->net);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
#ifdef EMBEDDED_LIBRARY
|
|
query_cache_insert(&thd->net, (char*)thd,
|
|
emb_count_querycache_size(thd));
|
|
#endif
|
|
|
|
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
|
|
|
bool interrupt;
|
|
query_cache.wait_while_table_flush_is_in_progress(&interrupt);
|
|
if (interrupt)
|
|
{
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
query_block= ((Query_cache_block*) thd->net.query_cache_query);
|
|
if (query_block)
|
|
{
|
|
/*
|
|
The writer is still present; finish last result block by chopping it to
|
|
suitable size if needed and setting block type. Since this is the last
|
|
block, the writer should be dropped.
|
|
*/
|
|
DUMP(&query_cache);
|
|
BLOCK_LOCK_WR(query_block);
|
|
Query_cache_query *header= query_block->query();
|
|
Query_cache_block *last_result_block;
|
|
ulong allign_size;
|
|
ulong len;
|
|
|
|
if (header->result() == 0)
|
|
{
|
|
DBUG_PRINT("error", ("End of data with no result blocks; "
|
|
"Query '%s' removed from cache.", header->query()));
|
|
/*
|
|
Extra safety: empty result should not happen in the normal call
|
|
to this function. In the release version that query should be ignored
|
|
and removed from QC.
|
|
*/
|
|
DBUG_ASSERT(0);
|
|
query_cache.free_query(query_block);
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
last_result_block= header->result()->prev;
|
|
allign_size= ALIGN_SIZE(last_result_block->used);
|
|
len= max(query_cache.min_allocation_unit, allign_size);
|
|
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->result()->type= Query_cache_block::RESULT;
|
|
|
|
/* Drop the writer. */
|
|
header->writer(0);
|
|
thd->net.query_cache_query= 0;
|
|
|
|
BLOCK_UNLOCK_WR(query_block);
|
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
|
|
|
}
|
|
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void query_cache_invalidate_by_MyISAM_filename(const char *filename)
|
|
{
|
|
query_cache.invalidate_by_MyISAM_filename(filename);
|
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(0););
|
|
}
|
|
|
|
|
|
/*
|
|
The following function forms part of the C plugin API
|
|
*/
|
|
extern "C"
|
|
void mysql_query_cache_invalidate4(THD *thd,
|
|
const char *key, unsigned key_length,
|
|
int using_trx)
|
|
{
|
|
query_cache.invalidate(thd, key, (uint32) key_length, (my_bool) using_trx);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Query_cache methods
|
|
*****************************************************************************/
|
|
|
|
Query_cache::Query_cache(ulong query_cache_limit_arg,
|
|
ulong min_allocation_unit_arg,
|
|
ulong min_result_data_size_arg,
|
|
uint def_query_hash_size_arg,
|
|
uint def_table_hash_size_arg)
|
|
:query_cache_size(0),
|
|
query_cache_limit(query_cache_limit_arg),
|
|
queries_in_cache(0), hits(0), inserts(0), refused(0),
|
|
total_blocks(0), lowmem_prunes(0),
|
|
min_allocation_unit(ALIGN_SIZE(min_allocation_unit_arg)),
|
|
min_result_data_size(ALIGN_SIZE(min_result_data_size_arg)),
|
|
def_query_hash_size(ALIGN_SIZE(def_query_hash_size_arg)),
|
|
def_table_hash_size(ALIGN_SIZE(def_table_hash_size_arg)),
|
|
initialized(0)
|
|
{
|
|
ulong min_needed= (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_block_table)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_query)) + 3);
|
|
set_if_bigger(min_allocation_unit,min_needed);
|
|
this->min_allocation_unit= ALIGN_SIZE(min_allocation_unit);
|
|
set_if_bigger(this->min_result_data_size,min_allocation_unit);
|
|
}
|
|
|
|
|
|
ulong Query_cache::resize(ulong query_cache_size_arg)
|
|
{
|
|
ulong new_query_cache_size;
|
|
DBUG_ENTER("Query_cache::resize");
|
|
DBUG_PRINT("qcache", ("from %lu to %lu",query_cache_size,
|
|
query_cache_size_arg));
|
|
DBUG_ASSERT(initialized);
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
while (is_flushing())
|
|
pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
|
|
m_cache_status= Query_cache::FLUSH_IN_PROGRESS;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
free_cache();
|
|
|
|
query_cache_size= query_cache_size_arg;
|
|
new_query_cache_size= init_cache();
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
|
|
pthread_cond_signal(&COND_cache_status_changed);
|
|
if (new_query_cache_size)
|
|
DBUG_EXECUTE("check_querycache",check_integrity(1););
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
DBUG_RETURN(new_query_cache_size);
|
|
}
|
|
|
|
|
|
ulong Query_cache::set_min_res_unit(ulong size)
|
|
{
|
|
if (size < min_allocation_unit)
|
|
size= min_allocation_unit;
|
|
return (min_result_data_size= ALIGN_SIZE(size));
|
|
}
|
|
|
|
|
|
void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used)
|
|
{
|
|
TABLE_COUNTER_TYPE local_tables;
|
|
ulong tot_length;
|
|
DBUG_ENTER("Query_cache::store_query");
|
|
/*
|
|
Testing 'query_cache_size' without a lock here is safe: the thing
|
|
we may loose is that the query won't be cached, but we save on
|
|
mutex locking in the case when query cache is disabled or the
|
|
query is uncachable.
|
|
|
|
See also a note on double-check locking usage above.
|
|
*/
|
|
if (thd->locked_tables || query_cache_size == 0)
|
|
DBUG_VOID_RETURN;
|
|
uint8 tables_type= 0;
|
|
|
|
if ((local_tables= is_cacheable(thd, thd->query_length,
|
|
thd->query, thd->lex, tables_used,
|
|
&tables_type)))
|
|
{
|
|
NET *net= &thd->net;
|
|
Query_cache_query_flags flags;
|
|
// fill all gaps between fields with 0 to get repeatable key
|
|
bzero(&flags, QUERY_CACHE_FLAGS_SIZE);
|
|
flags.client_long_flag= test(thd->client_capabilities & CLIENT_LONG_FLAG);
|
|
flags.client_protocol_41= test(thd->client_capabilities &
|
|
CLIENT_PROTOCOL_41);
|
|
/*
|
|
Protocol influences result format, so statement results in the binary
|
|
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.more_results_exists= test(thd->server_status &
|
|
SERVER_MORE_RESULTS_EXISTS);
|
|
flags.pkt_nr= net->pkt_nr;
|
|
flags.character_set_client_num=
|
|
thd->variables.character_set_client->number;
|
|
flags.character_set_results_num=
|
|
(thd->variables.character_set_results ?
|
|
thd->variables.character_set_results->number :
|
|
UINT_MAX);
|
|
flags.collation_connection_num=
|
|
thd->variables.collation_connection->number;
|
|
flags.limit= thd->variables.select_limit;
|
|
flags.time_zone= thd->variables.time_zone;
|
|
flags.sql_mode= thd->variables.sql_mode;
|
|
flags.max_sort_length= thd->variables.max_sort_length;
|
|
flags.lc_time_names= thd->variables.lc_time_names;
|
|
flags.group_concat_max_len= thd->variables.group_concat_max_len;
|
|
flags.div_precision_increment= thd->variables.div_precincrement;
|
|
flags.default_week_format= thd->variables.default_week_format;
|
|
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, \
|
|
def_week_frmt: %lu",
|
|
(int)flags.client_long_flag,
|
|
(int)flags.client_protocol_41,
|
|
(int)flags.result_in_binary_protocol,
|
|
(int)flags.more_results_exists,
|
|
flags.pkt_nr,
|
|
flags.character_set_client_num,
|
|
flags.character_set_results_num,
|
|
flags.collation_connection_num,
|
|
(ulong) flags.limit,
|
|
(ulong) flags.time_zone,
|
|
flags.sql_mode,
|
|
flags.max_sort_length,
|
|
flags.group_concat_max_len,
|
|
flags.div_precision_increment,
|
|
flags.default_week_format));
|
|
/*
|
|
Make InnoDB to release the adaptive hash index latch before
|
|
acquiring the query cache mutex.
|
|
*/
|
|
ha_release_temporary_latches(thd);
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size == 0 || is_flushing())
|
|
{
|
|
/*
|
|
A table- or a full flush operation can potentially take a long time to
|
|
finish. We choose not to wait for them and skip caching statements
|
|
instead.
|
|
*/
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
DUMP(this);
|
|
|
|
if (ask_handler_allowance(thd, tables_used))
|
|
{
|
|
refused++;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* Key is query + database + flag */
|
|
if (thd->db_length)
|
|
{
|
|
memcpy(thd->query+thd->query_length+1, thd->db, thd->db_length);
|
|
DBUG_PRINT("qcache", ("database: %s length: %u",
|
|
thd->db, thd->db_length));
|
|
}
|
|
else
|
|
{
|
|
DBUG_PRINT("qcache", ("No active database"));
|
|
}
|
|
tot_length= thd->query_length + thd->db_length + 1 +
|
|
QUERY_CACHE_FLAGS_SIZE;
|
|
/*
|
|
We should only copy structure (don't use it location directly)
|
|
because of alignment issue
|
|
*/
|
|
memcpy((void *)(thd->query + (tot_length - QUERY_CACHE_FLAGS_SIZE)),
|
|
&flags, QUERY_CACHE_FLAGS_SIZE);
|
|
|
|
/* Check if another thread is processing the same query? */
|
|
Query_cache_block *competitor = (Query_cache_block *)
|
|
hash_search(&queries, (uchar*) thd->query, tot_length);
|
|
DBUG_PRINT("qcache", ("competitor 0x%lx", (ulong) competitor));
|
|
if (competitor == 0)
|
|
{
|
|
/* Query is not in cache and no one is working with it; Store it */
|
|
Query_cache_block *query_block;
|
|
query_block= write_block_data(tot_length, (uchar*) thd->query,
|
|
ALIGN_SIZE(sizeof(Query_cache_query)),
|
|
Query_cache_block::QUERY, local_tables);
|
|
if (query_block != 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("query block 0x%lx allocated, %lu",
|
|
(ulong) query_block, query_block->used));
|
|
|
|
Query_cache_query *header = query_block->query();
|
|
header->init_n_lock();
|
|
if (my_hash_insert(&queries, (uchar*) query_block))
|
|
{
|
|
refused++;
|
|
DBUG_PRINT("qcache", ("insertion in query hash"));
|
|
header->unlock_n_destroy();
|
|
free_memory_block(query_block);
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
goto end;
|
|
}
|
|
if (!register_all_tables(query_block, tables_used, local_tables))
|
|
{
|
|
refused++;
|
|
DBUG_PRINT("warning", ("tables list including failed"));
|
|
hash_delete(&queries, (uchar *) query_block);
|
|
header->unlock_n_destroy();
|
|
free_memory_block(query_block);
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
goto end;
|
|
}
|
|
double_linked_list_simple_include(query_block, &queries_blocks);
|
|
inserts++;
|
|
queries_in_cache++;
|
|
net->query_cache_query= (uchar*) query_block;
|
|
header->writer(net);
|
|
header->tables_type(tables_type);
|
|
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
// init_n_lock make query block locked
|
|
BLOCK_UNLOCK_WR(query_block);
|
|
}
|
|
else
|
|
{
|
|
// We have not enough memory to store query => do nothing
|
|
refused++;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_PRINT("warning", ("Can't allocate query"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Another thread is processing the same query => do nothing
|
|
refused++;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_PRINT("qcache", ("Another thread process same query"));
|
|
}
|
|
}
|
|
else if (thd->lex->sql_command == SQLCOM_SELECT)
|
|
statistic_increment(refused, &structure_guard_mutex);
|
|
|
|
end:
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*
|
|
Check if the query is in the cache. If it was cached, send it
|
|
to the user.
|
|
|
|
RESULTS
|
|
1 Query was not cached.
|
|
0 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.
|
|
|
|
NOTE
|
|
This method requires that sql points to allocated memory of size:
|
|
tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE;
|
|
*/
|
|
|
|
int
|
|
Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)
|
|
{
|
|
ulonglong engine_data;
|
|
Query_cache_query *query;
|
|
Query_cache_block *first_result_block, *result_block;
|
|
Query_cache_block_table *block_table, *block_table_end;
|
|
ulong tot_length;
|
|
Query_cache_query_flags flags;
|
|
DBUG_ENTER("Query_cache::send_result_to_client");
|
|
|
|
/*
|
|
Testing 'query_cache_size' 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 (thd->locked_tables || thd->variables.query_cache_type == 0 ||
|
|
query_cache_size == 0)
|
|
goto err;
|
|
|
|
if (!thd->lex->safe_to_cache_query)
|
|
{
|
|
DBUG_PRINT("qcache", ("SELECT is non-cacheable"));
|
|
goto err;
|
|
}
|
|
|
|
{
|
|
uint i= 0;
|
|
/*
|
|
Skip '(' characters in queries like following:
|
|
(select a from t1) union (select a from t1);
|
|
*/
|
|
while (sql[i]=='(')
|
|
i++;
|
|
|
|
/*
|
|
Test if the query is a SELECT
|
|
(pre-space is removed in dispatch_command).
|
|
|
|
First '/' looks like comment before command it is not
|
|
frequently appeared in real life, consequently we can
|
|
check all such queries, too.
|
|
*/
|
|
if ((my_toupper(system_charset_info, sql[i]) != 'S' ||
|
|
my_toupper(system_charset_info, sql[i + 1]) != 'E' ||
|
|
my_toupper(system_charset_info, sql[i + 2]) != 'L') &&
|
|
sql[i] != '/')
|
|
{
|
|
DBUG_PRINT("qcache", ("The statement is not a SELECT; Not cached"));
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
|
|
if (query_cache_size == 0)
|
|
goto err_unlock;
|
|
|
|
if (is_flushing())
|
|
{
|
|
/* Return; Query cache is temporarily disabled while we flush. */
|
|
DBUG_PRINT("qcache",("query cache disabled"));
|
|
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;
|
|
|
|
tot_length= query_length + thd->db_length + 1 + QUERY_CACHE_FLAGS_SIZE;
|
|
if (thd->db_length)
|
|
{
|
|
memcpy(sql+query_length+1, thd->db, thd->db_length);
|
|
DBUG_PRINT("qcache", ("database: '%s' length: %u",
|
|
thd->db, thd->db_length));
|
|
}
|
|
else
|
|
{
|
|
DBUG_PRINT("qcache", ("No active database"));
|
|
}
|
|
|
|
// fill all gaps between fields with 0 to get repeatable key
|
|
bzero(&flags, QUERY_CACHE_FLAGS_SIZE);
|
|
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.more_results_exists= test(thd->server_status &
|
|
SERVER_MORE_RESULTS_EXISTS);
|
|
flags.pkt_nr= thd->net.pkt_nr;
|
|
flags.character_set_client_num= thd->variables.character_set_client->number;
|
|
flags.character_set_results_num=
|
|
(thd->variables.character_set_results ?
|
|
thd->variables.character_set_results->number :
|
|
UINT_MAX);
|
|
flags.collation_connection_num= thd->variables.collation_connection->number;
|
|
flags.limit= thd->variables.select_limit;
|
|
flags.time_zone= thd->variables.time_zone;
|
|
flags.sql_mode= thd->variables.sql_mode;
|
|
flags.max_sort_length= thd->variables.max_sort_length;
|
|
flags.group_concat_max_len= thd->variables.group_concat_max_len;
|
|
flags.div_precision_increment= thd->variables.div_precincrement;
|
|
flags.default_week_format= thd->variables.default_week_format;
|
|
flags.lc_time_names= thd->variables.lc_time_names;
|
|
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, \
|
|
def_week_frmt: %lu",
|
|
(int)flags.client_long_flag,
|
|
(int)flags.client_protocol_41,
|
|
(int)flags.result_in_binary_protocol,
|
|
(int)flags.more_results_exists,
|
|
flags.pkt_nr,
|
|
flags.character_set_client_num,
|
|
flags.character_set_results_num,
|
|
flags.collation_connection_num,
|
|
(ulong) flags.limit,
|
|
(ulong) flags.time_zone,
|
|
flags.sql_mode,
|
|
flags.max_sort_length,
|
|
flags.group_concat_max_len,
|
|
flags.div_precision_increment,
|
|
flags.default_week_format));
|
|
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);
|
|
/* Quick abort on unlocked data */
|
|
if (query_block == 0 ||
|
|
query_block->query()->result() == 0 ||
|
|
query_block->query()->result()->type != Query_cache_block::RESULT)
|
|
{
|
|
DBUG_PRINT("qcache", ("No query in query hash or no results"));
|
|
goto err_unlock;
|
|
}
|
|
DBUG_PRINT("qcache", ("Query in query hash 0x%lx", (ulong)query_block));
|
|
|
|
/* Now lock and test that nothing changed while blocks was unlocked */
|
|
BLOCK_LOCK_RD(query_block);
|
|
|
|
query = query_block->query();
|
|
result_block= first_result_block= query->result();
|
|
|
|
if (result_block == 0 || result_block->type != Query_cache_block::RESULT)
|
|
{
|
|
/* The query is probably yet processed */
|
|
DBUG_PRINT("qcache", ("query found, but no data or data incomplete"));
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
goto err_unlock;
|
|
}
|
|
DBUG_PRINT("qcache", ("Query have result 0x%lx", (ulong) query));
|
|
|
|
if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) &&
|
|
(query->tables_type() & HA_CACHE_TBL_TRANSACT))
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("we are in transaction and have transaction tables in query"));
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
goto err_unlock;
|
|
}
|
|
|
|
// Check access;
|
|
block_table= query_block->table(0);
|
|
block_table_end= block_table+query_block->n_tables;
|
|
for (; block_table != block_table_end; block_table++)
|
|
{
|
|
TABLE_LIST table_list;
|
|
TABLE *tmptable;
|
|
Query_cache_table *table = block_table->parent;
|
|
|
|
/*
|
|
Check that we have not temporary tables with same names of tables
|
|
of this query. If we have such tables, we will not send data from
|
|
query cache, because temporary tables hide real tables by which
|
|
query in query cache was made.
|
|
*/
|
|
for (tmptable= thd->temporary_tables; tmptable ; tmptable= tmptable->next)
|
|
{
|
|
if (tmptable->s->table_cache_key.length - TMP_TABLE_KEY_EXTRA ==
|
|
table->key_length() &&
|
|
!memcmp(tmptable->s->table_cache_key.str, table->data(),
|
|
table->key_length()))
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("Temporary table detected: '%s.%s'",
|
|
table_list.db, table_list.alias));
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
/*
|
|
We should not store result of this query because it contain
|
|
temporary tables => assign following variable to make check
|
|
faster.
|
|
*/
|
|
thd->lex->safe_to_cache_query=0;
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
DBUG_RETURN(-1);
|
|
}
|
|
}
|
|
|
|
bzero((char*) &table_list,sizeof(table_list));
|
|
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))
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("probably no SELECT access to %s.%s => return to normal processing",
|
|
table_list.db, table_list.alias));
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
thd->lex->safe_to_cache_query=0; // Don't try to cache this
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
DBUG_RETURN(-1); // Privilege error
|
|
}
|
|
if (table_list.grant.want_privilege)
|
|
{
|
|
DBUG_PRINT("qcache", ("Need to check column privileges for %s.%s",
|
|
table_list.db, table_list.alias));
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
thd->lex->safe_to_cache_query= 0; // Don't try to cache this
|
|
goto err_unlock; // Parse query
|
|
}
|
|
#endif /*!NO_EMBEDDED_ACCESS_CHECKS*/
|
|
engine_data= table->engine_data();
|
|
if (table->callback() &&
|
|
!(*table->callback())(thd, table->db(),
|
|
table->key_length(),
|
|
&engine_data))
|
|
{
|
|
DBUG_PRINT("qcache", ("Handler does not allow caching for %s.%s",
|
|
table_list.db, table_list.alias));
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
if (engine_data != table->engine_data())
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("Handler require invalidation queries of %s.%s %lu-%lu",
|
|
table_list.db, table_list.alias,
|
|
(ulong) engine_data, (ulong) table->engine_data()));
|
|
invalidate_table_internal(thd,
|
|
(uchar *) table->db(),
|
|
table->key_length());
|
|
}
|
|
else
|
|
thd->lex->safe_to_cache_query= 0; // Don't try to cache this
|
|
goto err_unlock; // Parse query
|
|
}
|
|
else
|
|
DBUG_PRINT("qcache", ("handler allow caching %s,%s",
|
|
table_list.db, table_list.alias));
|
|
}
|
|
move_to_query_list_end(query_block);
|
|
hits++;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
/*
|
|
Send cached result to client
|
|
*/
|
|
#ifndef EMBEDDED_LIBRARY
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("Results (len: %lu used: %lu headers: %lu)",
|
|
result_block->length, result_block->used,
|
|
(ulong) (result_block->headers_len()+
|
|
ALIGN_SIZE(sizeof(Query_cache_result)))));
|
|
|
|
Query_cache_result *result = result_block->result();
|
|
if (net_real_write(&thd->net, result->data(),
|
|
result_block->used -
|
|
result_block->headers_len() -
|
|
ALIGN_SIZE(sizeof(Query_cache_result))))
|
|
break; // Client aborted
|
|
result_block = result_block->next;
|
|
thd->net.pkt_nr= query->last_pkt_nr; // Keep packet number updated
|
|
} while (result_block != first_result_block);
|
|
#else
|
|
{
|
|
Querycache_stream qs(result_block, result_block->headers_len() +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
emb_load_querycache_result(thd, &qs);
|
|
}
|
|
#endif /*!EMBEDDED_LIBRARY*/
|
|
|
|
thd->limit_found_rows = query->found_rows();
|
|
thd->status_var.last_query_cost= 0.0;
|
|
thd->main_da.disable_status();
|
|
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
DBUG_RETURN(1); // Result sent to client
|
|
|
|
err_unlock:
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
err:
|
|
DBUG_RETURN(0); // Query was not cached
|
|
}
|
|
|
|
|
|
/*
|
|
Remove all cached queries that uses any of the tables in the list
|
|
*/
|
|
|
|
void Query_cache::invalidate(THD *thd, TABLE_LIST *tables_used,
|
|
my_bool using_transactions)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (table list)");
|
|
|
|
using_transactions= using_transactions &&
|
|
(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN));
|
|
for (; tables_used; tables_used= tables_used->next_local)
|
|
{
|
|
DBUG_ASSERT(!using_transactions || tables_used->table!=0);
|
|
if (tables_used->derived)
|
|
continue;
|
|
if (using_transactions &&
|
|
(tables_used->table->file->table_cache_type() ==
|
|
HA_CACHE_TBL_TRANSACT))
|
|
/*
|
|
tables_used->table can't be 0 in transaction.
|
|
Only 'drop' invalidate not opened table, but 'drop'
|
|
force transaction finish.
|
|
*/
|
|
thd->add_changed_table(tables_used->table);
|
|
else
|
|
invalidate_table(thd, tables_used);
|
|
}
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void Query_cache::invalidate(CHANGED_TABLE_LIST *tables_used)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (changed table list)");
|
|
THD *thd= current_thd;
|
|
for (; tables_used; tables_used= tables_used->next)
|
|
{
|
|
invalidate_table(thd, (uchar*) tables_used->key, tables_used->key_length);
|
|
DBUG_PRINT("qcache", ("db: %s table: %s", tables_used->key,
|
|
tables_used->key+
|
|
strlen(tables_used->key)+1));
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*
|
|
Invalidate locked for write
|
|
|
|
SYNOPSIS
|
|
Query_cache::invalidate_locked_for_write()
|
|
tables_used - table list
|
|
|
|
NOTE
|
|
can be used only for opened tables
|
|
*/
|
|
void Query_cache::invalidate_locked_for_write(TABLE_LIST *tables_used)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate_locked_for_write");
|
|
for (; tables_used; tables_used= tables_used->next_local)
|
|
{
|
|
if (tables_used->lock_type & (TL_WRITE_LOW_PRIORITY | TL_WRITE) &&
|
|
tables_used->table)
|
|
{
|
|
THD *thd= current_thd;
|
|
invalidate_table(thd, tables_used->table);
|
|
}
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*
|
|
Remove all cached queries that uses the given table
|
|
*/
|
|
|
|
void Query_cache::invalidate(THD *thd, TABLE *table,
|
|
my_bool using_transactions)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (table)");
|
|
|
|
using_transactions= using_transactions &&
|
|
(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN));
|
|
if (using_transactions &&
|
|
(table->file->table_cache_type() == HA_CACHE_TBL_TRANSACT))
|
|
thd->add_changed_table(table);
|
|
else
|
|
invalidate_table(thd, table);
|
|
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void Query_cache::invalidate(THD *thd, const char *key, uint32 key_length,
|
|
my_bool using_transactions)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (key)");
|
|
|
|
using_transactions= using_transactions &&
|
|
(thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN));
|
|
if (using_transactions) // used for innodb => has_transactions() is TRUE
|
|
thd->add_changed_table(key, key_length);
|
|
else
|
|
invalidate_table(thd, (uchar*)key, key_length);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Synchronize the thread with any flushing operations.
|
|
|
|
This helper function is called whenever a thread needs to operate on the
|
|
query cache structure (example: during invalidation). If a table flush is in
|
|
progress this function will wait for it to stop. If a full flush is in
|
|
progress, the function will set the interrupt parameter to indicate that the
|
|
current operation is redundant and should be interrupted.
|
|
|
|
@param[out] interrupt This out-parameter will be set to TRUE if the calling
|
|
function is redundant and should be interrupted.
|
|
|
|
@return If the interrupt-parameter is TRUE then m_cache_status is set to
|
|
NO_FLUSH_IN_PROGRESS. If the interrupt-parameter is FALSE then
|
|
m_cache_status is set to FLUSH_IN_PROGRESS.
|
|
The structure_guard_mutex will in any case be locked.
|
|
*/
|
|
|
|
void Query_cache::wait_while_table_flush_is_in_progress(bool *interrupt)
|
|
{
|
|
while (is_flushing())
|
|
{
|
|
/*
|
|
If there already is a full flush in progress query cache isn't enabled
|
|
and additional flushes are redundant; just return instead.
|
|
*/
|
|
if (m_cache_status == Query_cache::FLUSH_IN_PROGRESS)
|
|
{
|
|
*interrupt= TRUE;
|
|
return;
|
|
}
|
|
/*
|
|
If a table flush is in progress; wait on cache status to change.
|
|
*/
|
|
if (m_cache_status == Query_cache::TABLE_FLUSH_IN_PROGRESS)
|
|
pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
|
|
}
|
|
*interrupt= FALSE;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Remove all cached queries that uses the given database
|
|
*/
|
|
|
|
void Query_cache::invalidate(char *db)
|
|
{
|
|
bool restart= FALSE;
|
|
DBUG_ENTER("Query_cache::invalidate (db)");
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
bool interrupt;
|
|
wait_while_table_flush_is_in_progress(&interrupt);
|
|
if (interrupt)
|
|
{
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
return;
|
|
}
|
|
|
|
THD *thd= current_thd;
|
|
|
|
if (query_cache_size > 0)
|
|
{
|
|
if (tables_blocks)
|
|
{
|
|
Query_cache_block *table_block = tables_blocks;
|
|
do {
|
|
restart= FALSE;
|
|
do
|
|
{
|
|
Query_cache_block *next= table_block->next;
|
|
Query_cache_table *table = table_block->table();
|
|
if (strcmp(table->db(),db) == 0)
|
|
{
|
|
Query_cache_block_table *list_root= table_block->table(0);
|
|
invalidate_query_block_list(thd,list_root);
|
|
}
|
|
|
|
table_block= next;
|
|
|
|
/*
|
|
If our root node to used tables became null then the last element
|
|
in the table list was removed when a query was invalidated;
|
|
Terminate the search.
|
|
*/
|
|
if (tables_blocks == 0)
|
|
{
|
|
table_block= tables_blocks;
|
|
}
|
|
/*
|
|
If the iterated list has changed underlying structure;
|
|
we need to restart the search.
|
|
*/
|
|
else if (table_block->type == Query_cache_block::FREE)
|
|
{
|
|
restart= TRUE;
|
|
table_block= tables_blocks;
|
|
}
|
|
/*
|
|
The used tables are linked in a circular list;
|
|
loop until we return to the begining.
|
|
*/
|
|
} while (table_block != tables_blocks);
|
|
/*
|
|
Invalidating a table will also mean that all cached queries using
|
|
this table also will be invalidated. This will in turn change the
|
|
list of tables associated with these queries and the linked list of
|
|
used table will be changed. Because of this we might need to restart
|
|
the search when a table has been invalidated.
|
|
*/
|
|
} while (restart);
|
|
} // end if( tables_blocks )
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::invalidate_by_MyISAM_filename(const char *filename)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate_by_MyISAM_filename");
|
|
|
|
/* Calculate the key outside the lock to make the lock shorter */
|
|
char key[MAX_DBKEY_LENGTH];
|
|
uint32 db_length;
|
|
uint key_length= filename_2_table_key(key, filename, &db_length);
|
|
THD *thd= current_thd;
|
|
invalidate_table(thd,(uchar *)key, key_length);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* Remove all queries from cache */
|
|
|
|
void Query_cache::flush()
|
|
{
|
|
DBUG_ENTER("Query_cache::flush");
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size > 0)
|
|
{
|
|
DUMP(this);
|
|
flush_cache();
|
|
DUMP(this);
|
|
}
|
|
|
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Rearrange the memory blocks and join result in cache in 1 block (if
|
|
result length > join_limit)
|
|
|
|
@param[in] join_limit If the minimum length of a result block to be joined.
|
|
@param[in] iteration_limit The maximum number of packing and joining
|
|
sequences.
|
|
|
|
*/
|
|
|
|
void Query_cache::pack(ulong join_limit, uint iteration_limit)
|
|
{
|
|
DBUG_ENTER("Query_cache::pack");
|
|
|
|
bool interrupt;
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
wait_while_table_flush_is_in_progress(&interrupt);
|
|
if (interrupt)
|
|
{
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
if (query_cache_size == 0)
|
|
{
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
uint i = 0;
|
|
do
|
|
{
|
|
pack_cache();
|
|
} while ((++i < iteration_limit) && join_results(join_limit));
|
|
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::destroy()
|
|
{
|
|
DBUG_ENTER("Query_cache::destroy");
|
|
if (!initialized)
|
|
{
|
|
DBUG_PRINT("qcache", ("Query Cache not initialized"));
|
|
}
|
|
else
|
|
{
|
|
/* Underlying code expects the lock. */
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
free_cache();
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
pthread_cond_destroy(&COND_cache_status_changed);
|
|
pthread_mutex_destroy(&structure_guard_mutex);
|
|
initialized = 0;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
init/destroy
|
|
*****************************************************************************/
|
|
|
|
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);
|
|
m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
|
|
initialized = 1;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
ulong Query_cache::init_cache()
|
|
{
|
|
uint mem_bin_count, num, step;
|
|
ulong mem_bin_size, prev_size, inc;
|
|
ulong additional_data_size, max_mem_bin_size, approx_additional_data_size;
|
|
int align;
|
|
|
|
DBUG_ENTER("Query_cache::init_cache");
|
|
|
|
approx_additional_data_size = (sizeof(Query_cache) +
|
|
sizeof(uchar*)*(def_query_hash_size+
|
|
def_table_hash_size));
|
|
if (query_cache_size < approx_additional_data_size)
|
|
goto err;
|
|
|
|
query_cache_size-= approx_additional_data_size;
|
|
align= query_cache_size % ALIGN_SIZE(1);
|
|
if (align)
|
|
{
|
|
query_cache_size-= align;
|
|
approx_additional_data_size+= align;
|
|
}
|
|
|
|
/*
|
|
Count memory bins number.
|
|
Check section 6. in start comment for the used algorithm.
|
|
*/
|
|
|
|
max_mem_bin_size = query_cache_size >> QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2;
|
|
mem_bin_count = (uint) ((1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
|
|
QUERY_CACHE_MEM_BIN_PARTS_MUL);
|
|
mem_bin_num = 1;
|
|
mem_bin_steps = 1;
|
|
mem_bin_size = max_mem_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2;
|
|
prev_size = 0;
|
|
if (mem_bin_size <= min_allocation_unit)
|
|
{
|
|
DBUG_PRINT("qcache", ("too small query cache => query cache disabled"));
|
|
// TODO here (and above) should be warning in 4.1
|
|
goto err;
|
|
}
|
|
while (mem_bin_size > min_allocation_unit)
|
|
{
|
|
mem_bin_num += mem_bin_count;
|
|
prev_size = mem_bin_size;
|
|
mem_bin_size >>= QUERY_CACHE_MEM_BIN_STEP_PWR2;
|
|
mem_bin_steps++;
|
|
mem_bin_count += QUERY_CACHE_MEM_BIN_PARTS_INC;
|
|
mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL);
|
|
|
|
// Prevent too small bins spacing
|
|
if (mem_bin_count > (mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2))
|
|
mem_bin_count= (mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2);
|
|
}
|
|
inc = (prev_size - mem_bin_size) / mem_bin_count;
|
|
mem_bin_num += (mem_bin_count - (min_allocation_unit - mem_bin_size)/inc);
|
|
mem_bin_steps++;
|
|
additional_data_size = ((mem_bin_num+1) *
|
|
ALIGN_SIZE(sizeof(Query_cache_memory_bin))+
|
|
(mem_bin_steps *
|
|
ALIGN_SIZE(sizeof(Query_cache_memory_bin_step))));
|
|
|
|
if (query_cache_size < additional_data_size)
|
|
goto err;
|
|
query_cache_size -= additional_data_size;
|
|
|
|
if (!(cache= (uchar *)
|
|
my_malloc_lock(query_cache_size+additional_data_size, MYF(0))))
|
|
goto err;
|
|
|
|
DBUG_PRINT("qcache", ("cache length %lu, min unit %lu, %u bins",
|
|
query_cache_size, min_allocation_unit, mem_bin_num));
|
|
|
|
steps = (Query_cache_memory_bin_step *) cache;
|
|
bins = ((Query_cache_memory_bin *)
|
|
(cache + mem_bin_steps *
|
|
ALIGN_SIZE(sizeof(Query_cache_memory_bin_step))));
|
|
|
|
first_block = (Query_cache_block *) (cache + additional_data_size);
|
|
first_block->init(query_cache_size);
|
|
total_blocks++;
|
|
first_block->pnext=first_block->pprev=first_block;
|
|
first_block->next=first_block->prev=first_block;
|
|
|
|
/* Prepare bins */
|
|
|
|
bins[0].init(max_mem_bin_size);
|
|
steps[0].init(max_mem_bin_size,0,0);
|
|
mem_bin_count = (uint) ((1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
|
|
QUERY_CACHE_MEM_BIN_PARTS_MUL);
|
|
num= step= 1;
|
|
mem_bin_size = max_mem_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2;
|
|
while (mem_bin_size > min_allocation_unit)
|
|
{
|
|
ulong incr = (steps[step-1].size - mem_bin_size) / mem_bin_count;
|
|
unsigned long size = mem_bin_size;
|
|
for (uint i= mem_bin_count; i > 0; i--)
|
|
{
|
|
bins[num+i-1].init(size);
|
|
size += incr;
|
|
}
|
|
num += mem_bin_count;
|
|
steps[step].init(mem_bin_size, num-1, incr);
|
|
mem_bin_size >>= QUERY_CACHE_MEM_BIN_STEP_PWR2;
|
|
step++;
|
|
mem_bin_count += QUERY_CACHE_MEM_BIN_PARTS_INC;
|
|
mem_bin_count = (uint) (mem_bin_count * QUERY_CACHE_MEM_BIN_PARTS_MUL);
|
|
if (mem_bin_count > (mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2))
|
|
mem_bin_count=(mem_bin_size >> QUERY_CACHE_MEM_BIN_SPC_LIM_PWR2);
|
|
}
|
|
inc = (steps[step-1].size - mem_bin_size) / mem_bin_count;
|
|
|
|
/*
|
|
num + mem_bin_count > mem_bin_num, but index never be > mem_bin_num
|
|
because block with size < min_allocated_unit never will be requested
|
|
*/
|
|
|
|
steps[step].init(mem_bin_size, num + mem_bin_count - 1, inc);
|
|
{
|
|
uint skiped = (min_allocation_unit - mem_bin_size)/inc;
|
|
ulong size = mem_bin_size + inc*skiped;
|
|
uint i = mem_bin_count - skiped;
|
|
while (i-- > 0)
|
|
{
|
|
bins[num+i].init(size);
|
|
size += inc;
|
|
}
|
|
}
|
|
bins[mem_bin_num].number = 1; // For easy end test in get_free_block
|
|
free_memory = free_memory_blocks = 0;
|
|
insert_into_free_memory_list(first_block);
|
|
|
|
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
|
|
/*
|
|
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
|
|
comparison (no matter if file system case sensitive or not).
|
|
If we have case-sensitive file system (like on most Unixes) and
|
|
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));
|
|
#else
|
|
/*
|
|
On windows, OS/2, MacOS X with HFS+ or any other case insensitive
|
|
file system if lower_case_table_names!=0 we have same situation as
|
|
in previous case, but if lower_case_table_names==0 then we should
|
|
not distinguish cases (to be compatible in behavior with underlying
|
|
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));
|
|
#endif
|
|
|
|
queries_in_cache = 0;
|
|
queries_blocks = 0;
|
|
DBUG_RETURN(query_cache_size +
|
|
additional_data_size + approx_additional_data_size);
|
|
|
|
err:
|
|
make_disabled();
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
/* Disable the use of the query cache */
|
|
|
|
void Query_cache::make_disabled()
|
|
{
|
|
DBUG_ENTER("Query_cache::make_disabled");
|
|
query_cache_size= 0;
|
|
queries_blocks= 0;
|
|
free_memory= 0;
|
|
bins= 0;
|
|
steps= 0;
|
|
cache= 0;
|
|
mem_bin_num= mem_bin_steps= 0;
|
|
queries_in_cache= 0;
|
|
first_block= 0;
|
|
total_blocks= 0;
|
|
tables_blocks= 0;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/**
|
|
@class Query_cache
|
|
@brief Free all resources allocated by the cache.
|
|
|
|
This function frees all resources allocated by the cache. You
|
|
have to call init_cache() before using the cache again. This function
|
|
requires the structure_guard_mutex to be locked.
|
|
*/
|
|
|
|
void Query_cache::free_cache()
|
|
{
|
|
DBUG_ENTER("Query_cache::free_cache");
|
|
|
|
my_free((uchar*) cache, MYF(MY_ALLOW_ZERO_PTR));
|
|
make_disabled();
|
|
hash_free(&queries);
|
|
hash_free(&tables);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Free block data
|
|
*****************************************************************************/
|
|
|
|
|
|
/**
|
|
@brief Flush the cache.
|
|
|
|
This function will flush cache contents. It assumes we have
|
|
'structure_guard_mutex' locked. The function sets the m_cache_status flag and
|
|
releases the lock, so other threads may proceed skipping the cache as if it
|
|
is disabled. Concurrent flushes are performed in turn.
|
|
After flush_cache() call, the cache is flushed, all the freed memory is
|
|
accumulated in bin[0], and the 'structure_guard_mutex' is locked. However,
|
|
since we could release the mutex during execution, the rest of the cache
|
|
state could have been changed, and should not be relied on.
|
|
*/
|
|
|
|
void Query_cache::flush_cache()
|
|
{
|
|
/*
|
|
If there is flush in progress, wait for it to finish, and then do
|
|
our flush. This is necessary because something could be added to
|
|
the cache before we acquire the lock again, and some code (like
|
|
Query_cache::free_cache()) depends on the fact that after the
|
|
flush the cache is empty.
|
|
*/
|
|
while (is_flushing())
|
|
pthread_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
|
|
|
|
/*
|
|
Setting 'FLUSH_IN_PROGRESS' will prevent other threads from using
|
|
the cache while we are in the middle of the flush, and we release
|
|
the lock so that other threads won't block.
|
|
*/
|
|
m_cache_status= Query_cache::FLUSH_IN_PROGRESS;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
my_hash_reset(&queries);
|
|
while (queries_blocks != 0)
|
|
{
|
|
BLOCK_LOCK_WR(queries_blocks);
|
|
free_query_internal(queries_blocks);
|
|
}
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
|
|
pthread_cond_signal(&COND_cache_status_changed);
|
|
}
|
|
|
|
/*
|
|
Free oldest query that is not in use by another thread.
|
|
Returns 1 if we couldn't remove anything
|
|
*/
|
|
|
|
my_bool Query_cache::free_old_query()
|
|
{
|
|
DBUG_ENTER("Query_cache::free_old_query");
|
|
if (queries_blocks)
|
|
{
|
|
/*
|
|
try_lock_writing used to prevent client because here lock
|
|
sequence is breached.
|
|
Also we don't need remove locked queries at this point.
|
|
*/
|
|
Query_cache_block *query_block= 0;
|
|
if (queries_blocks != 0)
|
|
{
|
|
Query_cache_block *block = queries_blocks;
|
|
/* Search until we find first query that we can remove */
|
|
do
|
|
{
|
|
Query_cache_query *header = block->query();
|
|
if (header->result() != 0 &&
|
|
header->result()->type == Query_cache_block::RESULT &&
|
|
block->query()->try_lock_writing())
|
|
{
|
|
query_block = block;
|
|
break;
|
|
}
|
|
} while ((block=block->next) != queries_blocks );
|
|
}
|
|
|
|
if (query_block != 0)
|
|
{
|
|
free_query(query_block);
|
|
lowmem_prunes++;
|
|
DBUG_RETURN(0);
|
|
}
|
|
}
|
|
DBUG_RETURN(1); // Nothing to remove
|
|
}
|
|
|
|
|
|
/*
|
|
free_query_internal() - free query from query cache.
|
|
|
|
SYNOPSIS
|
|
free_query_internal()
|
|
query_block Query_cache_block representing the query
|
|
|
|
DESCRIPTION
|
|
This function will remove the query from a cache, and place its
|
|
memory blocks to the list of free blocks. 'query_block' must be
|
|
locked for writing, this function will release (and destroy) this
|
|
lock.
|
|
|
|
NOTE
|
|
'query_block' should be removed from 'queries' hash _before_
|
|
calling this method, as the lock will be destroyed here.
|
|
*/
|
|
|
|
void Query_cache::free_query_internal(Query_cache_block *query_block)
|
|
{
|
|
DBUG_ENTER("Query_cache::free_query_internal");
|
|
DBUG_PRINT("qcache", ("free query 0x%lx %lu bytes result",
|
|
(ulong) query_block,
|
|
query_block->query()->length() ));
|
|
|
|
queries_in_cache--;
|
|
|
|
Query_cache_query *query= query_block->query();
|
|
|
|
if (query->writer() != 0)
|
|
{
|
|
/* Tell MySQL that this query should not be cached anymore */
|
|
query->writer()->query_cache_query= 0;
|
|
query->writer(0);
|
|
}
|
|
double_linked_list_exclude(query_block, &queries_blocks);
|
|
Query_cache_block_table *table= query_block->table(0);
|
|
|
|
for (TABLE_COUNTER_TYPE i= 0; i < query_block->n_tables; i++)
|
|
unlink_table(table++);
|
|
Query_cache_block *result_block= query->result();
|
|
|
|
/*
|
|
The following is true when query destruction was called and no results
|
|
in query . (query just registered and then abort/pack/flush called)
|
|
*/
|
|
if (result_block != 0)
|
|
{
|
|
if (result_block->type != Query_cache_block::RESULT)
|
|
{
|
|
// removing unfinished query
|
|
refused++;
|
|
inserts--;
|
|
}
|
|
Query_cache_block *block= result_block;
|
|
do
|
|
{
|
|
Query_cache_block *current= block;
|
|
block= block->next;
|
|
free_memory_block(current);
|
|
} while (block != result_block);
|
|
}
|
|
else
|
|
{
|
|
// removing unfinished query
|
|
refused++;
|
|
inserts--;
|
|
}
|
|
|
|
query->unlock_n_destroy();
|
|
free_memory_block(query_block);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*
|
|
free_query() - free query from query cache.
|
|
|
|
SYNOPSIS
|
|
free_query()
|
|
query_block Query_cache_block representing the query
|
|
|
|
DESCRIPTION
|
|
This function will remove 'query_block' from 'queries' hash, and
|
|
then call free_query_internal(), which see.
|
|
*/
|
|
|
|
void Query_cache::free_query(Query_cache_block *query_block)
|
|
{
|
|
DBUG_ENTER("Query_cache::free_query");
|
|
DBUG_PRINT("qcache", ("free query 0x%lx %lu bytes result",
|
|
(ulong) query_block,
|
|
query_block->query()->length() ));
|
|
|
|
hash_delete(&queries,(uchar *) query_block);
|
|
free_query_internal(query_block);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Query data creation
|
|
*****************************************************************************/
|
|
|
|
Query_cache_block *
|
|
Query_cache::write_block_data(ulong data_len, uchar* data,
|
|
ulong header_len,
|
|
Query_cache_block::block_type type,
|
|
TABLE_COUNTER_TYPE ntab)
|
|
{
|
|
ulong all_headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(ntab*sizeof(Query_cache_block_table)) +
|
|
header_len);
|
|
ulong len = data_len + all_headers_len;
|
|
ulong align_len= ALIGN_SIZE(len);
|
|
DBUG_ENTER("Query_cache::write_block_data");
|
|
DBUG_PRINT("qcache", ("data: %ld, header: %ld, all header: %ld",
|
|
data_len, header_len, all_headers_len));
|
|
Query_cache_block *block= allocate_block(max(align_len,
|
|
min_allocation_unit),1, 0);
|
|
if (block != 0)
|
|
{
|
|
block->type = type;
|
|
block->n_tables = ntab;
|
|
block->used = len;
|
|
|
|
memcpy((uchar *) block+ all_headers_len, data, data_len);
|
|
}
|
|
DBUG_RETURN(block);
|
|
}
|
|
|
|
|
|
/*
|
|
On success STRUCT_UNLOCK(&query_cache.structure_guard_mutex) will be done.
|
|
*/
|
|
|
|
my_bool
|
|
Query_cache::append_result_data(Query_cache_block **current_block,
|
|
ulong data_len, uchar* data,
|
|
Query_cache_block *query_block)
|
|
{
|
|
DBUG_ENTER("Query_cache::append_result_data");
|
|
DBUG_PRINT("qcache", ("append %lu bytes to 0x%lx query",
|
|
data_len, (long) query_block));
|
|
|
|
if (query_block->query()->add(data_len) > query_cache_limit)
|
|
{
|
|
DBUG_PRINT("qcache", ("size limit reached %lu > %lu",
|
|
query_block->query()->length(),
|
|
query_cache_limit));
|
|
DBUG_RETURN(0);
|
|
}
|
|
if (*current_block == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("allocated first result data block %lu", data_len));
|
|
/*
|
|
STRUCT_UNLOCK(&structure_guard_mutex) Will be done by
|
|
write_result_data if success;
|
|
*/
|
|
DBUG_RETURN(write_result_data(current_block, data_len, data, query_block,
|
|
Query_cache_block::RES_BEG));
|
|
}
|
|
Query_cache_block *last_block = (*current_block)->prev;
|
|
|
|
DBUG_PRINT("qcache", ("lastblock 0x%lx len %lu used %lu",
|
|
(ulong) last_block, last_block->length,
|
|
last_block->used));
|
|
my_bool success = 1;
|
|
ulong last_block_free_space= last_block->length - last_block->used;
|
|
|
|
/*
|
|
We will first allocate and write the 'tail' of data, that doesn't fit
|
|
in the 'last_block'. Only if this succeeds, we will fill the last_block.
|
|
This saves us a memcpy if the query doesn't fit in the query cache.
|
|
*/
|
|
|
|
// Try join blocks if physically next block is free...
|
|
ulong tail = data_len - last_block_free_space;
|
|
ulong append_min = get_min_append_result_data_size();
|
|
if (last_block_free_space < data_len &&
|
|
append_next_free_block(last_block,
|
|
max(tail, append_min)))
|
|
last_block_free_space = last_block->length - last_block->used;
|
|
// If no space in last block (even after join) allocate new block
|
|
if (last_block_free_space < data_len)
|
|
{
|
|
DBUG_PRINT("qcache", ("allocate new block for %lu bytes",
|
|
data_len-last_block_free_space));
|
|
Query_cache_block *new_block = 0;
|
|
/*
|
|
On success STRUCT_UNLOCK(&structure_guard_mutex) will be done
|
|
by the next call
|
|
*/
|
|
success = write_result_data(&new_block, data_len-last_block_free_space,
|
|
(uchar*)(((uchar*)data)+last_block_free_space),
|
|
query_block,
|
|
Query_cache_block::RES_CONT);
|
|
/*
|
|
new_block may be != 0 even !success (if write_result_data
|
|
allocate a small block but failed to allocate continue)
|
|
*/
|
|
if (new_block != 0)
|
|
double_linked_list_join(last_block, new_block);
|
|
}
|
|
else
|
|
{
|
|
// It is success (nobody can prevent us write data)
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
|
|
// Now finally write data to the last block
|
|
if (success && last_block_free_space > 0)
|
|
{
|
|
ulong to_copy = min(data_len,last_block_free_space);
|
|
DBUG_PRINT("qcache", ("use free space %lub at block 0x%lx to copy %lub",
|
|
last_block_free_space, (ulong)last_block, to_copy));
|
|
memcpy((uchar*) last_block + last_block->used, data, to_copy);
|
|
last_block->used+=to_copy;
|
|
}
|
|
DBUG_RETURN(success);
|
|
}
|
|
|
|
|
|
my_bool Query_cache::write_result_data(Query_cache_block **result_block,
|
|
ulong data_len, uchar* data,
|
|
Query_cache_block *query_block,
|
|
Query_cache_block::block_type type)
|
|
{
|
|
DBUG_ENTER("Query_cache::write_result_data");
|
|
DBUG_PRINT("qcache", ("data_len %lu",data_len));
|
|
|
|
/*
|
|
Reserve block(s) for filling
|
|
During data allocation we must have structure_guard_mutex locked.
|
|
As data copy is not a fast operation, it's better if we don't have
|
|
structure_guard_mutex locked during data coping.
|
|
Thus we first allocate space and lock query, then unlock
|
|
structure_guard_mutex and copy data.
|
|
*/
|
|
|
|
my_bool success = allocate_data_chain(result_block, data_len, query_block,
|
|
type == Query_cache_block::RES_BEG);
|
|
if (success)
|
|
{
|
|
// It is success (nobody can prevent us write data)
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
#ifndef EMBEDDED_LIBRARY
|
|
Query_cache_block *block= *result_block;
|
|
uchar *rest= data;
|
|
// Now fill list of blocks that created by allocate_data_chain
|
|
do
|
|
{
|
|
block->type = type;
|
|
ulong length = block->used - headers_len;
|
|
DBUG_PRINT("qcache", ("write %lu byte in block 0x%lx",length,
|
|
(ulong)block));
|
|
memcpy((uchar*) block+headers_len, rest, length);
|
|
rest += length;
|
|
block = block->next;
|
|
type = Query_cache_block::RES_CONT;
|
|
} while (block != *result_block);
|
|
#else
|
|
/*
|
|
Set type of first block, emb_store_querycache_result() will handle
|
|
the others.
|
|
*/
|
|
(*result_block)->type= type;
|
|
Querycache_stream qs(*result_block, headers_len);
|
|
emb_store_querycache_result(&qs, (THD*)data);
|
|
#endif /*!EMBEDDED_LIBRARY*/
|
|
}
|
|
else
|
|
{
|
|
if (*result_block != 0)
|
|
{
|
|
// Destroy list of blocks that was created & locked by lock_result_data
|
|
Query_cache_block *block = *result_block;
|
|
do
|
|
{
|
|
Query_cache_block *current = block;
|
|
block = block->next;
|
|
free_memory_block(current);
|
|
} while (block != *result_block);
|
|
*result_block = 0;
|
|
/*
|
|
It is not success => not unlock structure_guard_mutex (we need it to
|
|
free query)
|
|
*/
|
|
}
|
|
}
|
|
DBUG_PRINT("qcache", ("success %d", (int) success));
|
|
DBUG_RETURN(success);
|
|
}
|
|
|
|
inline ulong Query_cache::get_min_first_result_data_size()
|
|
{
|
|
if (queries_in_cache < QUERY_CACHE_MIN_ESTIMATED_QUERIES_NUMBER)
|
|
return min_result_data_size;
|
|
ulong avg_result = (query_cache_size - free_memory) / queries_in_cache;
|
|
avg_result = min(avg_result, query_cache_limit);
|
|
return max(min_result_data_size, avg_result);
|
|
}
|
|
|
|
inline ulong Query_cache::get_min_append_result_data_size()
|
|
{
|
|
return min_result_data_size;
|
|
}
|
|
|
|
/*
|
|
Allocate one or more blocks to hold data
|
|
*/
|
|
my_bool Query_cache::allocate_data_chain(Query_cache_block **result_block,
|
|
ulong data_len,
|
|
Query_cache_block *query_block,
|
|
my_bool first_block_arg)
|
|
{
|
|
ulong all_headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
ulong min_size = (first_block_arg ?
|
|
get_min_first_result_data_size():
|
|
get_min_append_result_data_size());
|
|
Query_cache_block *prev_block= NULL;
|
|
Query_cache_block *new_block;
|
|
DBUG_ENTER("Query_cache::allocate_data_chain");
|
|
DBUG_PRINT("qcache", ("data_len %lu, all_headers_len %lu",
|
|
data_len, all_headers_len));
|
|
|
|
do
|
|
{
|
|
ulong len= data_len + all_headers_len;
|
|
ulong align_len= ALIGN_SIZE(len);
|
|
|
|
if (!(new_block= allocate_block(max(min_size, align_len),
|
|
min_result_data_size == 0,
|
|
all_headers_len + min_result_data_size)))
|
|
{
|
|
DBUG_PRINT("warning", ("Can't allocate block for results"));
|
|
DBUG_RETURN(FALSE);
|
|
}
|
|
|
|
new_block->n_tables = 0;
|
|
new_block->used = min(len, new_block->length);
|
|
new_block->type = Query_cache_block::RES_INCOMPLETE;
|
|
new_block->next = new_block->prev = new_block;
|
|
Query_cache_result *header = new_block->result();
|
|
header->parent(query_block);
|
|
|
|
DBUG_PRINT("qcache", ("Block len %lu used %lu",
|
|
new_block->length, new_block->used));
|
|
|
|
if (prev_block)
|
|
double_linked_list_join(prev_block, new_block);
|
|
else
|
|
*result_block= new_block;
|
|
if (new_block->length >= len)
|
|
break;
|
|
|
|
/*
|
|
We got less memory then we need (no big memory blocks) =>
|
|
Continue to allocated more blocks until we got everything we need.
|
|
*/
|
|
data_len= len - new_block->length;
|
|
prev_block= new_block;
|
|
} while (1);
|
|
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Tables management
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
Invalidate the first table in the table_list
|
|
*/
|
|
|
|
void Query_cache::invalidate_table(THD *thd, TABLE_LIST *table_list)
|
|
{
|
|
if (table_list->table != 0)
|
|
invalidate_table(thd, table_list->table); // Table is open
|
|
else
|
|
{
|
|
char key[MAX_DBKEY_LENGTH];
|
|
uint key_length;
|
|
|
|
key_length=(uint) (strmov(strmov(key,table_list->db)+1,
|
|
table_list->table_name) -key)+ 1;
|
|
|
|
// We don't store temporary tables => no key_length+=4 ...
|
|
invalidate_table(thd, (uchar *)key, key_length);
|
|
}
|
|
}
|
|
|
|
void Query_cache::invalidate_table(THD *thd, TABLE *table)
|
|
{
|
|
invalidate_table(thd, (uchar*) table->s->table_cache_key.str,
|
|
table->s->table_cache_key.length);
|
|
}
|
|
|
|
void Query_cache::invalidate_table(THD *thd, uchar * key, uint32 key_length)
|
|
{
|
|
bool interrupt;
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
wait_while_table_flush_is_in_progress(&interrupt);
|
|
if (interrupt)
|
|
{
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
Setting 'TABLE_FLUSH_IN_PROGRESS' will temporarily disable the cache
|
|
so that structural changes to cache won't block the entire server.
|
|
However, threads requesting to change the query cache will still have
|
|
to wait for the flush to finish.
|
|
*/
|
|
m_cache_status= Query_cache::TABLE_FLUSH_IN_PROGRESS;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
if (query_cache_size > 0)
|
|
invalidate_table_internal(thd, key, key_length);
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
m_cache_status= Query_cache::NO_FLUSH_IN_PROGRESS;
|
|
|
|
/*
|
|
net_real_write might be waiting on a change on the m_cache_status
|
|
variable.
|
|
*/
|
|
pthread_cond_signal(&COND_cache_status_changed);
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
|
|
|
|
/**
|
|
Try to locate and invalidate a table by name.
|
|
The caller must ensure that no other thread is trying to work with
|
|
the query cache when this function is executed.
|
|
|
|
@pre structure_guard_mutex is acquired or TABLE_FLUSH_IN_PROGRESS is set.
|
|
*/
|
|
|
|
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);
|
|
if (table_block)
|
|
{
|
|
Query_cache_block_table *list_root= table_block->table(0);
|
|
invalidate_query_block_list(thd, list_root);
|
|
}
|
|
}
|
|
|
|
/**
|
|
@brief Invalidate a linked list of query cache blocks.
|
|
|
|
Each block tries to aquire a block level lock before
|
|
free_query is a called. This function will in turn affect
|
|
related table- and result-blocks.
|
|
|
|
@param[in,out] thd Thread context.
|
|
@param[in,out] list_root A pointer to a circular list of query blocks.
|
|
|
|
*/
|
|
|
|
void
|
|
Query_cache::invalidate_query_block_list(THD *thd,
|
|
Query_cache_block_table *list_root)
|
|
{
|
|
while (list_root->next != list_root)
|
|
{
|
|
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););
|
|
}
|
|
}
|
|
|
|
/*
|
|
Register given table list begining with given position in tables table of
|
|
block
|
|
|
|
SYNOPSIS
|
|
Query_cache::register_tables_from_list
|
|
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
|
|
|
|
RETURN
|
|
0 error
|
|
number of next position of table entry in table of tables of block
|
|
*/
|
|
|
|
TABLE_COUNTER_TYPE
|
|
Query_cache::register_tables_from_list(TABLE_LIST *tables_used,
|
|
TABLE_COUNTER_TYPE counter,
|
|
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++)
|
|
{
|
|
if (tables_used->derived && !tables_used->view)
|
|
{
|
|
DBUG_PRINT("qcache", ("derived table skipped"));
|
|
n--;
|
|
block_table--;
|
|
continue;
|
|
}
|
|
block_table->n= n;
|
|
if (tables_used->view)
|
|
{
|
|
char key[MAX_DBKEY_LENGTH];
|
|
uint key_length;
|
|
DBUG_PRINT("qcache", ("view: %s db: %s",
|
|
tables_used->view_name.str,
|
|
tables_used->view_db.str));
|
|
key_length= (uint) (strmov(strmov(key, tables_used->view_db.str) + 1,
|
|
tables_used->view_name.str) - key) + 1;
|
|
/*
|
|
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))
|
|
DBUG_RETURN(0);
|
|
/*
|
|
We do not need to register view tables here because they are already
|
|
present in the global list.
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("table: %s db: %s openinfo: 0x%lx keylen: %lu key: 0x%lx",
|
|
tables_used->table->s->table_name.str,
|
|
tables_used->table->s->table_cache_key.str,
|
|
(ulong) tables_used->table,
|
|
(ulong) tables_used->table->s->table_cache_key.length,
|
|
(ulong) tables_used->table->s->table_cache_key.str));
|
|
|
|
if (!insert_table(tables_used->table->s->table_cache_key.length,
|
|
tables_used->table->s->table_cache_key.str,
|
|
block_table,
|
|
tables_used->db_length,
|
|
tables_used->table->file->table_cache_type(),
|
|
tables_used->callback_func,
|
|
tables_used->engine_data))
|
|
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
|
|
}
|
|
}
|
|
DBUG_RETURN(n - counter);
|
|
}
|
|
|
|
/*
|
|
Store all used tables
|
|
|
|
SYNOPSIS
|
|
register_all_tables()
|
|
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,
|
|
TABLE_LIST *tables_used,
|
|
TABLE_COUNTER_TYPE tables_arg)
|
|
{
|
|
TABLE_COUNTER_TYPE n;
|
|
DBUG_PRINT("qcache", ("register tables block 0x%lx, n %d, header %x",
|
|
(ulong) block, (int) tables_arg,
|
|
(int) ALIGN_SIZE(sizeof(Query_cache_block))));
|
|
|
|
Query_cache_block_table *block_table = block->table(0);
|
|
|
|
n= register_tables_from_list(tables_used, 0, block_table);
|
|
|
|
if (n==0)
|
|
{
|
|
/* Unlink the tables we allocated above */
|
|
for (Query_cache_block_table *tmp = block->table(0) ;
|
|
tmp != block_table;
|
|
tmp++)
|
|
unlink_table(tmp);
|
|
}
|
|
return (n);
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Insert used table name into the cache.
|
|
|
|
@return Error status
|
|
@retval FALSE On error
|
|
@retval TRUE On success
|
|
*/
|
|
|
|
my_bool
|
|
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)
|
|
{
|
|
DBUG_ENTER("Query_cache::insert_table");
|
|
DBUG_PRINT("qcache", ("insert table node 0x%lx, len %d",
|
|
(ulong)node, key_len));
|
|
|
|
THD *thd= current_thd;
|
|
|
|
Query_cache_block *table_block=
|
|
(Query_cache_block *)hash_search(&tables, (uchar*) key, key_len);
|
|
|
|
if (table_block &&
|
|
table_block->table()->engine_data() != engine_data)
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("Handler require invalidation queries of %s.%s %lu-%lu",
|
|
table_block->table()->db(),
|
|
table_block->table()->table(),
|
|
(ulong) engine_data,
|
|
(ulong) table_block->table()->engine_data()));
|
|
/*
|
|
as far as we delete all queries with this table, table block will be
|
|
deleted, too
|
|
*/
|
|
{
|
|
Query_cache_block_table *list_root= table_block->table(0);
|
|
invalidate_query_block_list(thd, list_root);
|
|
}
|
|
|
|
table_block= 0;
|
|
}
|
|
|
|
if (table_block == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("new table block from 0x%lx (%u)",
|
|
(ulong) key, (int) key_len));
|
|
table_block= write_block_data(key_len, (uchar*) key,
|
|
ALIGN_SIZE(sizeof(Query_cache_table)),
|
|
Query_cache_block::TABLE, 1);
|
|
if (table_block == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("Can't write table name to cache"));
|
|
DBUG_RETURN(0);
|
|
}
|
|
Query_cache_table *header= table_block->table();
|
|
double_linked_list_simple_include(table_block,
|
|
&tables_blocks);
|
|
/*
|
|
First node in the Query_cache_block_table-chain is the table-type
|
|
block. This block will only have one Query_cache_block_table (n=0).
|
|
*/
|
|
Query_cache_block_table *list_root= table_block->table(0);
|
|
list_root->n= 0;
|
|
|
|
/*
|
|
The node list is circular in nature.
|
|
*/
|
|
list_root->next= list_root->prev= list_root;
|
|
|
|
if (my_hash_insert(&tables, (const uchar *) table_block))
|
|
{
|
|
DBUG_PRINT("qcache", ("Can't insert table to hash"));
|
|
// write_block_data return locked block
|
|
free_memory_block(table_block);
|
|
DBUG_RETURN(0);
|
|
}
|
|
char *db= header->db();
|
|
header->table(db + db_length + 1);
|
|
header->key_length(key_len);
|
|
header->type(cache_type);
|
|
header->callback(callback);
|
|
header->engine_data(engine_data);
|
|
|
|
/*
|
|
We insert this table without the assumption that it isn't refrenenced by
|
|
any queries.
|
|
*/
|
|
header->m_cached_query_count= 0;
|
|
}
|
|
|
|
/*
|
|
Table is now in the cache; link the table_block-node associated
|
|
with the currently processed query into the chain of queries depending
|
|
on the cached table.
|
|
*/
|
|
Query_cache_block_table *list_root= table_block->table(0);
|
|
node->next= list_root->next;
|
|
list_root->next= node;
|
|
node->next->prev= node;
|
|
node->prev= list_root;
|
|
node->parent= table_block->table();
|
|
/*
|
|
Increase the counter to keep track on how long this chain
|
|
of queries is.
|
|
*/
|
|
Query_cache_table *table_block_data= table_block->table();
|
|
table_block_data->m_cached_query_count++;
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
|
|
void Query_cache::unlink_table(Query_cache_block_table *node)
|
|
{
|
|
DBUG_ENTER("Query_cache::unlink_table");
|
|
node->prev->next= node->next;
|
|
node->next->prev= node->prev;
|
|
Query_cache_block_table *neighbour= node->next;
|
|
Query_cache_table *table_block_data= node->parent;
|
|
table_block_data->m_cached_query_count--;
|
|
|
|
DBUG_ASSERT(table_block_data->m_cached_query_count >= 0);
|
|
|
|
if (neighbour->next == neighbour)
|
|
{
|
|
DBUG_ASSERT(table_block_data->m_cached_query_count == 0);
|
|
/*
|
|
If neighbor is root of list, the list is empty.
|
|
The root of the list is always a table-type block
|
|
which contain exactly one Query_cache_block_table
|
|
node object, thus we can use the block() method
|
|
to calculate the Query_cache_block address.
|
|
*/
|
|
Query_cache_block *table_block= neighbour->block();
|
|
double_linked_list_exclude(table_block,
|
|
&tables_blocks);
|
|
hash_delete(&tables,(uchar *) table_block);
|
|
free_memory_block(table_block);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Free memory management
|
|
*****************************************************************************/
|
|
|
|
Query_cache_block *
|
|
Query_cache::allocate_block(ulong len, my_bool not_less, ulong min)
|
|
{
|
|
DBUG_ENTER("Query_cache::allocate_block");
|
|
DBUG_PRINT("qcache", ("len %lu, not less %d, min %lu",
|
|
len, not_less,min));
|
|
|
|
if (len >= min(query_cache_size, query_cache_limit))
|
|
{
|
|
DBUG_PRINT("qcache", ("Query cache hase only %lu memory and limit %lu",
|
|
query_cache_size, query_cache_limit));
|
|
DBUG_RETURN(0); // in any case we don't have such piece of memory
|
|
}
|
|
|
|
/* Free old queries until we have enough memory to store this block */
|
|
Query_cache_block *block;
|
|
do
|
|
{
|
|
block= get_free_block(len, not_less, min);
|
|
}
|
|
while (block == 0 && !free_old_query());
|
|
|
|
if (block != 0) // If we found a suitable block
|
|
{
|
|
if (block->length >= ALIGN_SIZE(len) + min_allocation_unit)
|
|
split_block(block,ALIGN_SIZE(len));
|
|
}
|
|
|
|
DBUG_RETURN(block);
|
|
}
|
|
|
|
|
|
Query_cache_block *
|
|
Query_cache::get_free_block(ulong len, my_bool not_less, ulong min)
|
|
{
|
|
Query_cache_block *block = 0, *first = 0;
|
|
DBUG_ENTER("Query_cache::get_free_block");
|
|
DBUG_PRINT("qcache",("length %lu, not_less %d, min %lu", len,
|
|
(int)not_less, min));
|
|
|
|
/* Find block with minimal size > len */
|
|
uint start = find_bin(len);
|
|
// try matching bin
|
|
if (bins[start].number != 0)
|
|
{
|
|
Query_cache_block *list = bins[start].free_blocks;
|
|
if (list->prev->length >= len) // check block with max size
|
|
{
|
|
first = list;
|
|
uint n = 0;
|
|
while ( n < QUERY_CACHE_MEM_BIN_TRY &&
|
|
first->length < len) //we don't need irst->next != list
|
|
{
|
|
first=first->next;
|
|
n++;
|
|
}
|
|
if (first->length >= len)
|
|
block=first;
|
|
else // we don't need if (first->next != list)
|
|
{
|
|
n = 0;
|
|
block = list->prev;
|
|
while (n < QUERY_CACHE_MEM_BIN_TRY &&
|
|
block->length > len)
|
|
{
|
|
block=block->prev;
|
|
n++;
|
|
}
|
|
if (block->length < len)
|
|
block=block->next;
|
|
}
|
|
}
|
|
else
|
|
first = list->prev;
|
|
}
|
|
if (block == 0 && start > 0)
|
|
{
|
|
DBUG_PRINT("qcache",("Try bins with bigger block size"));
|
|
// Try more big bins
|
|
int i = start - 1;
|
|
while (i > 0 && bins[i].number == 0)
|
|
i--;
|
|
if (bins[i].number > 0)
|
|
block = bins[i].free_blocks;
|
|
}
|
|
|
|
// If no big blocks => try less size (if it is possible)
|
|
if (block == 0 && ! not_less)
|
|
{
|
|
DBUG_PRINT("qcache",("Try to allocate a smaller block"));
|
|
if (first != 0 && first->length > min)
|
|
block = first;
|
|
else
|
|
{
|
|
uint i = start + 1;
|
|
/* bins[mem_bin_num].number contains 1 for easy end test */
|
|
for (i= start+1 ; bins[i].number == 0 ; i++) ;
|
|
if (i < mem_bin_num && bins[i].free_blocks->prev->length >= min)
|
|
block = bins[i].free_blocks->prev;
|
|
}
|
|
}
|
|
if (block != 0)
|
|
exclude_from_free_memory_list(block);
|
|
|
|
DBUG_PRINT("qcache",("getting block 0x%lx", (ulong) block));
|
|
DBUG_RETURN(block);
|
|
}
|
|
|
|
|
|
void Query_cache::free_memory_block(Query_cache_block *block)
|
|
{
|
|
DBUG_ENTER("Query_cache::free_memory_block");
|
|
block->used=0;
|
|
block->type= Query_cache_block::FREE; // mark block as free in any case
|
|
DBUG_PRINT("qcache",
|
|
("first_block 0x%lx, block 0x%lx, pnext 0x%lx pprev 0x%lx",
|
|
(ulong) first_block, (ulong) block, (ulong) block->pnext,
|
|
(ulong) block->pprev));
|
|
|
|
if (block->pnext != first_block && block->pnext->is_free())
|
|
block = join_free_blocks(block, block->pnext);
|
|
if (block != first_block && block->pprev->is_free())
|
|
block = join_free_blocks(block->pprev, block->pprev);
|
|
insert_into_free_memory_list(block);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::split_block(Query_cache_block *block, ulong len)
|
|
{
|
|
DBUG_ENTER("Query_cache::split_block");
|
|
Query_cache_block *new_block = (Query_cache_block*)(((uchar*) block)+len);
|
|
|
|
new_block->init(block->length - len);
|
|
total_blocks++;
|
|
block->length=len;
|
|
new_block->pnext = block->pnext;
|
|
block->pnext = new_block;
|
|
new_block->pprev = block;
|
|
new_block->pnext->pprev = new_block;
|
|
|
|
if (block->type == Query_cache_block::FREE)
|
|
{
|
|
// if block was free then it already joined with all free neighbours
|
|
insert_into_free_memory_list(new_block);
|
|
}
|
|
else
|
|
free_memory_block(new_block);
|
|
|
|
DBUG_PRINT("qcache", ("split 0x%lx (%lu) new 0x%lx",
|
|
(ulong) block, len, (ulong) new_block));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
Query_cache_block *
|
|
Query_cache::join_free_blocks(Query_cache_block *first_block_arg,
|
|
Query_cache_block *block_in_list)
|
|
{
|
|
Query_cache_block *second_block;
|
|
DBUG_ENTER("Query_cache::join_free_blocks");
|
|
DBUG_PRINT("qcache",
|
|
("join first 0x%lx, pnext 0x%lx, in list 0x%lx",
|
|
(ulong) first_block_arg, (ulong) first_block_arg->pnext,
|
|
(ulong) block_in_list));
|
|
|
|
exclude_from_free_memory_list(block_in_list);
|
|
second_block = first_block_arg->pnext;
|
|
// May be was not free block
|
|
second_block->used=0;
|
|
second_block->destroy();
|
|
total_blocks--;
|
|
|
|
first_block_arg->length += second_block->length;
|
|
first_block_arg->pnext = second_block->pnext;
|
|
second_block->pnext->pprev = first_block_arg;
|
|
|
|
DBUG_RETURN(first_block_arg);
|
|
}
|
|
|
|
|
|
my_bool Query_cache::append_next_free_block(Query_cache_block *block,
|
|
ulong add_size)
|
|
{
|
|
Query_cache_block *next_block = block->pnext;
|
|
DBUG_ENTER("Query_cache::append_next_free_block");
|
|
DBUG_PRINT("enter", ("block 0x%lx, add_size %lu", (ulong) block,
|
|
add_size));
|
|
|
|
if (next_block != first_block && next_block->is_free())
|
|
{
|
|
ulong old_len = block->length;
|
|
exclude_from_free_memory_list(next_block);
|
|
next_block->destroy();
|
|
total_blocks--;
|
|
|
|
block->length += next_block->length;
|
|
block->pnext = next_block->pnext;
|
|
next_block->pnext->pprev = block;
|
|
|
|
if (block->length > ALIGN_SIZE(old_len + add_size) + min_allocation_unit)
|
|
split_block(block,ALIGN_SIZE(old_len + add_size));
|
|
DBUG_PRINT("exit", ("block was appended"));
|
|
DBUG_RETURN(1);
|
|
}
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
void Query_cache::exclude_from_free_memory_list(Query_cache_block *free_block)
|
|
{
|
|
DBUG_ENTER("Query_cache::exclude_from_free_memory_list");
|
|
Query_cache_memory_bin *bin = *((Query_cache_memory_bin **)
|
|
free_block->data());
|
|
double_linked_list_exclude(free_block, &bin->free_blocks);
|
|
bin->number--;
|
|
free_memory-=free_block->length;
|
|
free_memory_blocks--;
|
|
DBUG_PRINT("qcache",("exclude block 0x%lx, bin 0x%lx", (ulong) free_block,
|
|
(ulong) bin));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void Query_cache::insert_into_free_memory_list(Query_cache_block *free_block)
|
|
{
|
|
DBUG_ENTER("Query_cache::insert_into_free_memory_list");
|
|
uint idx = find_bin(free_block->length);
|
|
insert_into_free_memory_sorted_list(free_block, &bins[idx].free_blocks);
|
|
/*
|
|
We have enough memory in block for storing bin reference due to
|
|
min_allocation_unit choice
|
|
*/
|
|
Query_cache_memory_bin **bin_ptr = ((Query_cache_memory_bin**)
|
|
free_block->data());
|
|
*bin_ptr = bins+idx;
|
|
(*bin_ptr)->number++;
|
|
DBUG_PRINT("qcache",("insert block 0x%lx, bin[%d] 0x%lx",
|
|
(ulong) free_block, idx, (ulong) *bin_ptr));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
uint Query_cache::find_bin(ulong size)
|
|
{
|
|
DBUG_ENTER("Query_cache::find_bin");
|
|
// Binary search
|
|
int left = 0, right = mem_bin_steps;
|
|
do
|
|
{
|
|
int middle = (left + right) / 2;
|
|
if (steps[middle].size > size)
|
|
left = middle+1;
|
|
else
|
|
right = middle;
|
|
} while (left < right);
|
|
if (left == 0)
|
|
{
|
|
// first bin not subordinate of common rules
|
|
DBUG_PRINT("qcache", ("first bin (# 0), size %lu",size));
|
|
DBUG_RETURN(0);
|
|
}
|
|
uint bin = steps[left].idx -
|
|
(uint)((size - steps[left].size)/steps[left].increment);
|
|
|
|
DBUG_PRINT("qcache", ("bin %u step %u, size %lu step size %lu",
|
|
bin, left, size, steps[left].size));
|
|
DBUG_RETURN(bin);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Lists management
|
|
*****************************************************************************/
|
|
|
|
void Query_cache::move_to_query_list_end(Query_cache_block *query_block)
|
|
{
|
|
DBUG_ENTER("Query_cache::move_to_query_list_end");
|
|
double_linked_list_exclude(query_block, &queries_blocks);
|
|
double_linked_list_simple_include(query_block, &queries_blocks);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::insert_into_free_memory_sorted_list(Query_cache_block *
|
|
new_block,
|
|
Query_cache_block **
|
|
list)
|
|
{
|
|
DBUG_ENTER("Query_cache::insert_into_free_memory_sorted_list");
|
|
/*
|
|
list sorted by size in ascendant order, because we need small blocks
|
|
more frequently than bigger ones
|
|
*/
|
|
|
|
new_block->used = 0;
|
|
new_block->n_tables = 0;
|
|
new_block->type = Query_cache_block::FREE;
|
|
|
|
if (*list == 0)
|
|
{
|
|
*list = new_block->next=new_block->prev=new_block;
|
|
DBUG_PRINT("qcache", ("inserted into empty list"));
|
|
}
|
|
else
|
|
{
|
|
Query_cache_block *point = *list;
|
|
if (point->length >= new_block->length)
|
|
{
|
|
point = point->prev;
|
|
*list = new_block;
|
|
}
|
|
else
|
|
{
|
|
/* Find right position in sorted list to put block */
|
|
while (point->next != *list &&
|
|
point->next->length < new_block->length)
|
|
point=point->next;
|
|
}
|
|
new_block->prev = point;
|
|
new_block->next = point->next;
|
|
new_block->next->prev = new_block;
|
|
point->next = new_block;
|
|
}
|
|
free_memory+=new_block->length;
|
|
free_memory_blocks++;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void
|
|
Query_cache::double_linked_list_simple_include(Query_cache_block *point,
|
|
Query_cache_block **
|
|
list_pointer)
|
|
{
|
|
DBUG_ENTER("Query_cache::double_linked_list_simple_include");
|
|
DBUG_PRINT("qcache", ("including block 0x%lx", (ulong) point));
|
|
if (*list_pointer == 0)
|
|
*list_pointer=point->next=point->prev=point;
|
|
else
|
|
{
|
|
// insert to the end of list
|
|
point->next = (*list_pointer);
|
|
point->prev = (*list_pointer)->prev;
|
|
point->prev->next = point;
|
|
(*list_pointer)->prev = point;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void
|
|
Query_cache::double_linked_list_exclude(Query_cache_block *point,
|
|
Query_cache_block **list_pointer)
|
|
{
|
|
DBUG_ENTER("Query_cache::double_linked_list_exclude");
|
|
DBUG_PRINT("qcache", ("excluding block 0x%lx, list 0x%lx",
|
|
(ulong) point, (ulong) list_pointer));
|
|
if (point->next == point)
|
|
*list_pointer = 0; // empty list
|
|
else
|
|
{
|
|
point->next->prev = point->prev;
|
|
point->prev->next = point->next;
|
|
/*
|
|
If the root is removed; select a new root
|
|
*/
|
|
if (point == *list_pointer)
|
|
*list_pointer= point->next;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::double_linked_list_join(Query_cache_block *head_tail,
|
|
Query_cache_block *tail_head)
|
|
{
|
|
Query_cache_block *head_head = head_tail->next,
|
|
*tail_tail = tail_head->prev;
|
|
head_head->prev = tail_tail;
|
|
head_tail->next = tail_head;
|
|
tail_head->prev = head_tail;
|
|
tail_tail->next = head_head;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Query
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
Collect information about table types, check that tables are cachable and
|
|
count them
|
|
|
|
SYNOPSIS
|
|
process_and_count_tables()
|
|
tables_used table list for processing
|
|
tables_type pointer to variable for table types collection
|
|
|
|
RETURN
|
|
0 error
|
|
>0 number of tables
|
|
*/
|
|
|
|
TABLE_COUNTER_TYPE
|
|
Query_cache::process_and_count_tables(THD *thd, TABLE_LIST *tables_used,
|
|
uint8 *tables_type)
|
|
{
|
|
DBUG_ENTER("process_and_count_tables");
|
|
TABLE_COUNTER_TYPE table_count = 0;
|
|
for (; tables_used; tables_used= tables_used->next_global)
|
|
{
|
|
table_count++;
|
|
#ifndef NO_EMBEDDED_ACCESS_CHECKS
|
|
/*
|
|
Disable any attempt to store this statement if there are
|
|
column level grants on any referenced tables.
|
|
The grant.want_privileges flag was set to 1 in the
|
|
check_grant() function earlier if the TABLE_LIST object
|
|
had any associated column privileges.
|
|
|
|
We need to check that the TABLE_LIST object isn't part
|
|
of a VIEW definition because we want to be able to cache
|
|
views.
|
|
|
|
TODO: Although it is possible to cache views, the privilege
|
|
check on view tables always fall back on column privileges
|
|
even if there are more generic table privileges. Thus it isn't
|
|
currently possible to retrieve cached view-tables unless the
|
|
client has the super user privileges.
|
|
*/
|
|
if (tables_used->grant.want_privilege &&
|
|
tables_used->belong_to_view == NULL)
|
|
{
|
|
DBUG_PRINT("qcache", ("Don't cache statement as it refers to "
|
|
"tables with column privileges."));
|
|
thd->lex->safe_to_cache_query= 0;
|
|
DBUG_RETURN(0);
|
|
}
|
|
#endif
|
|
if (tables_used->view)
|
|
{
|
|
DBUG_PRINT("qcache", ("view: %s db: %s",
|
|
tables_used->view_name.str,
|
|
tables_used->view_db.str));
|
|
*tables_type|= HA_CACHE_TBL_NONTRANSACT;
|
|
}
|
|
else
|
|
{
|
|
DBUG_PRINT("qcache", ("table: %s db: %s type: %u",
|
|
tables_used->table->s->table_name.str,
|
|
tables_used->table->s->db.str,
|
|
tables_used->table->s->db_type()->db_type));
|
|
if (tables_used->derived)
|
|
{
|
|
table_count--;
|
|
DBUG_PRINT("qcache", ("derived table skipped"));
|
|
continue;
|
|
}
|
|
*tables_type|= tables_used->table->file->table_cache_type();
|
|
|
|
/*
|
|
table_alias_charset used here because it depends of
|
|
lower_case_table_names variable
|
|
*/
|
|
if (tables_used->table->s->tmp_table != NO_TMP_TABLE ||
|
|
(*tables_type & HA_CACHE_TBL_NOCACHE) ||
|
|
(tables_used->db_length == 5 &&
|
|
my_strnncoll(table_alias_charset,
|
|
(uchar*)tables_used->table->s->table_cache_key.str, 6,
|
|
(uchar*)"mysql",6) == 0))
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("select not cacheable: temporary, system or "
|
|
"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);
|
|
}
|
|
|
|
|
|
/*
|
|
If query is cacheable return number tables in query
|
|
(query without tables are not cached)
|
|
*/
|
|
|
|
TABLE_COUNTER_TYPE
|
|
Query_cache::is_cacheable(THD *thd, uint32 query_len, char *query, LEX *lex,
|
|
TABLE_LIST *tables_used, uint8 *tables_type)
|
|
{
|
|
TABLE_COUNTER_TYPE table_count;
|
|
DBUG_ENTER("Query_cache::is_cacheable");
|
|
|
|
if (query_cache_is_cacheable_query(lex) &&
|
|
(thd->variables.query_cache_type == 1 ||
|
|
(thd->variables.query_cache_type == 2 && (lex->select_lex.options &
|
|
OPTION_TO_QUERY_CACHE))))
|
|
{
|
|
DBUG_PRINT("qcache", ("options: %lx %lx type: %u",
|
|
(long) OPTION_TO_QUERY_CACHE,
|
|
(long) lex->select_lex.options,
|
|
(int) thd->variables.query_cache_type));
|
|
|
|
if (!(table_count= process_and_count_tables(thd, tables_used,
|
|
tables_type)))
|
|
DBUG_RETURN(0);
|
|
|
|
if ((thd->options & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) &&
|
|
((*tables_type)&HA_CACHE_TBL_TRANSACT))
|
|
{
|
|
DBUG_PRINT("qcache", ("not in autocommin mode"));
|
|
DBUG_RETURN(0);
|
|
}
|
|
DBUG_PRINT("qcache", ("select is using %d tables", table_count));
|
|
DBUG_RETURN(table_count);
|
|
}
|
|
|
|
DBUG_PRINT("qcache",
|
|
("not interesting query: %d or not cacheable, options %lx %lx type: %u",
|
|
(int) lex->sql_command,
|
|
(long) OPTION_TO_QUERY_CACHE,
|
|
(long) lex->select_lex.options,
|
|
(int) thd->variables.query_cache_type));
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
/*
|
|
Check handler allowance to cache query with these tables
|
|
|
|
SYNOPSYS
|
|
Query_cache::ask_handler_allowance()
|
|
thd - thread handlers
|
|
tables_used - tables list used in query
|
|
|
|
RETURN
|
|
0 - caching allowed
|
|
1 - caching disallowed
|
|
*/
|
|
my_bool Query_cache::ask_handler_allowance(THD *thd,
|
|
TABLE_LIST *tables_used)
|
|
{
|
|
DBUG_ENTER("Query_cache::ask_handler_allowance");
|
|
|
|
for (; tables_used; tables_used= tables_used->next_global)
|
|
{
|
|
TABLE *table;
|
|
handler *handler;
|
|
if (!(table= tables_used->table))
|
|
continue;
|
|
handler= table->file;
|
|
if (!handler->register_query_cache_table(thd,
|
|
table->s->table_cache_key.str,
|
|
table->s->table_cache_key.length,
|
|
&tables_used->callback_func,
|
|
&tables_used->engine_data))
|
|
{
|
|
DBUG_PRINT("qcache", ("Handler does not allow caching for %s.%s",
|
|
tables_used->db, tables_used->alias));
|
|
thd->lex->safe_to_cache_query= 0; // Don't try to cache this
|
|
DBUG_RETURN(1);
|
|
}
|
|
}
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Packing
|
|
*****************************************************************************/
|
|
|
|
|
|
/**
|
|
@brief Rearrange all memory blocks so that free memory joins at the
|
|
'bottom' of the allocated memory block containing all cache data.
|
|
@see Query_cache::pack(ulong join_limit, uint iteration_limit)
|
|
*/
|
|
|
|
void Query_cache::pack_cache()
|
|
{
|
|
DBUG_ENTER("Query_cache::pack_cache");
|
|
|
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
|
|
|
uchar *border = 0;
|
|
Query_cache_block *before = 0;
|
|
ulong gap = 0;
|
|
my_bool ok = 1;
|
|
Query_cache_block *block = first_block;
|
|
DUMP(this);
|
|
|
|
if (first_block)
|
|
{
|
|
do
|
|
{
|
|
Query_cache_block *next=block->pnext;
|
|
ok = move_by_type(&border, &before, &gap, block);
|
|
block = next;
|
|
} while (ok && block != first_block);
|
|
|
|
if (border != 0)
|
|
{
|
|
Query_cache_block *new_block = (Query_cache_block *) border;
|
|
new_block->init(gap);
|
|
total_blocks++;
|
|
new_block->pnext = before->pnext;
|
|
before->pnext = new_block;
|
|
new_block->pprev = before;
|
|
new_block->pnext->pprev = new_block;
|
|
insert_into_free_memory_list(new_block);
|
|
}
|
|
DUMP(this);
|
|
}
|
|
|
|
DBUG_EXECUTE("check_querycache",query_cache.check_integrity(1););
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
my_bool Query_cache::move_by_type(uchar **border,
|
|
Query_cache_block **before, ulong *gap,
|
|
Query_cache_block *block)
|
|
{
|
|
DBUG_ENTER("Query_cache::move_by_type");
|
|
|
|
my_bool ok = 1;
|
|
switch (block->type) {
|
|
case Query_cache_block::FREE:
|
|
{
|
|
DBUG_PRINT("qcache", ("block 0x%lx FREE", (ulong) block));
|
|
if (*border == 0)
|
|
{
|
|
*border = (uchar *) block;
|
|
*before = block->pprev;
|
|
DBUG_PRINT("qcache", ("gap beginning here"));
|
|
}
|
|
exclude_from_free_memory_list(block);
|
|
*gap +=block->length;
|
|
block->pprev->pnext=block->pnext;
|
|
block->pnext->pprev=block->pprev;
|
|
block->destroy();
|
|
total_blocks--;
|
|
DBUG_PRINT("qcache", ("added to gap (%lu)", *gap));
|
|
break;
|
|
}
|
|
case Query_cache_block::TABLE:
|
|
{
|
|
HASH_SEARCH_STATE record_idx;
|
|
DBUG_PRINT("qcache", ("block 0x%lx TABLE", (ulong) block));
|
|
if (*border == 0)
|
|
break;
|
|
ulong len = block->length, used = block->used;
|
|
Query_cache_block_table *list_root = block->table(0);
|
|
Query_cache_block_table *tprev = list_root->prev,
|
|
*tnext = list_root->next;
|
|
Query_cache_block *prev = block->prev,
|
|
*next = block->next,
|
|
*pprev = block->pprev,
|
|
*pnext = block->pnext,
|
|
*new_block =(Query_cache_block *) *border;
|
|
uint tablename_offset = block->table()->table() - block->table()->db();
|
|
char *data = (char*) block->data();
|
|
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);
|
|
|
|
block->destroy();
|
|
new_block->init(len);
|
|
new_block->type=Query_cache_block::TABLE;
|
|
new_block->used=used;
|
|
new_block->n_tables=1;
|
|
memmove((char*) new_block->data(), data, len-new_block->headers_len());
|
|
relink(block, new_block, next, prev, pnext, pprev);
|
|
if (tables_blocks == block)
|
|
tables_blocks = new_block;
|
|
|
|
Query_cache_block_table *nlist_root = new_block->table(0);
|
|
nlist_root->n = 0;
|
|
nlist_root->next = tnext;
|
|
tnext->prev = nlist_root;
|
|
nlist_root->prev = tprev;
|
|
tprev->next = nlist_root;
|
|
DBUG_PRINT("qcache",
|
|
("list_root: 0x%lx tnext 0x%lx tprev 0x%lx tprev->next 0x%lx tnext->prev 0x%lx",
|
|
(ulong) list_root, (ulong) tnext, (ulong) tprev,
|
|
(ulong)tprev->next, (ulong)tnext->prev));
|
|
/*
|
|
Go through all queries that uses this table and change them to
|
|
point to the new table object
|
|
*/
|
|
Query_cache_table *new_block_table=new_block->table();
|
|
for (;tnext != nlist_root; tnext=tnext->next)
|
|
tnext->parent= new_block_table;
|
|
*border += len;
|
|
*before = new_block;
|
|
/* 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);
|
|
|
|
DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
|
|
len, (ulong) new_block, (ulong) *border));
|
|
break;
|
|
}
|
|
case Query_cache_block::QUERY:
|
|
{
|
|
HASH_SEARCH_STATE record_idx;
|
|
DBUG_PRINT("qcache", ("block 0x%lx QUERY", (ulong) block));
|
|
if (*border == 0)
|
|
break;
|
|
BLOCK_LOCK_WR(block);
|
|
ulong len = block->length, used = block->used;
|
|
TABLE_COUNTER_TYPE n_tables = block->n_tables;
|
|
Query_cache_block *prev = block->prev,
|
|
*next = block->next,
|
|
*pprev = block->pprev,
|
|
*pnext = block->pnext,
|
|
*new_block =(Query_cache_block*) *border;
|
|
char *data = (char*) block->data();
|
|
Query_cache_block *first_result_block = ((Query_cache_query *)
|
|
block->data())->result();
|
|
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)));
|
|
block->query()->unlock_n_destroy();
|
|
block->destroy();
|
|
new_block->init(len);
|
|
new_block->type=Query_cache_block::QUERY;
|
|
new_block->used=used;
|
|
new_block->n_tables=n_tables;
|
|
memmove((char*) new_block->data(), data, len - new_block->headers_len());
|
|
relink(block, new_block, next, prev, pnext, pprev);
|
|
if (queries_blocks == block)
|
|
queries_blocks = new_block;
|
|
Query_cache_block_table *beg_of_table_table= block->table(0),
|
|
*end_of_table_table= block->table(n_tables);
|
|
uchar *beg_of_new_table_table= (uchar*) new_block->table(0);
|
|
|
|
for (TABLE_COUNTER_TYPE j=0; j < n_tables; j++)
|
|
{
|
|
Query_cache_block_table *block_table = new_block->table(j);
|
|
|
|
// use aligment from begining of table if 'next' is in same block
|
|
if ((beg_of_table_table <= block_table->next) &&
|
|
(block_table->next < end_of_table_table))
|
|
((Query_cache_block_table *)(beg_of_new_table_table +
|
|
(((uchar*)block_table->next) -
|
|
((uchar*)beg_of_table_table))))->prev=
|
|
block_table;
|
|
else
|
|
block_table->next->prev= block_table;
|
|
|
|
// use aligment from begining of table if 'prev' is in same block
|
|
if ((beg_of_table_table <= block_table->prev) &&
|
|
(block_table->prev < end_of_table_table))
|
|
((Query_cache_block_table *)(beg_of_new_table_table +
|
|
(((uchar*)block_table->prev) -
|
|
((uchar*)beg_of_table_table))))->next=
|
|
block_table;
|
|
else
|
|
block_table->prev->next = block_table;
|
|
}
|
|
DBUG_PRINT("qcache", ("after circle tt"));
|
|
*border += len;
|
|
*before = new_block;
|
|
new_block->query()->result(first_result_block);
|
|
if (first_result_block != 0)
|
|
{
|
|
Query_cache_block *result_block = first_result_block;
|
|
do
|
|
{
|
|
result_block->result()->parent(new_block);
|
|
result_block = result_block->next;
|
|
} while ( result_block != first_result_block );
|
|
}
|
|
Query_cache_query *new_query= ((Query_cache_query *) new_block->data());
|
|
my_rwlock_init(&new_query->lock, NULL);
|
|
|
|
/*
|
|
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)
|
|
{
|
|
net->query_cache_query= (uchar*) new_block;
|
|
}
|
|
/* Fix hash to point at moved block */
|
|
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;
|
|
}
|
|
case Query_cache_block::RES_INCOMPLETE:
|
|
case Query_cache_block::RES_BEG:
|
|
case Query_cache_block::RES_CONT:
|
|
case Query_cache_block::RESULT:
|
|
{
|
|
DBUG_PRINT("qcache", ("block 0x%lx RES* (%d)", (ulong) block,
|
|
(int) block->type));
|
|
if (*border == 0)
|
|
break;
|
|
Query_cache_block *query_block = block->result()->parent(),
|
|
*next = block->next,
|
|
*prev = block->prev;
|
|
Query_cache_block::block_type type = block->type;
|
|
BLOCK_LOCK_WR(query_block);
|
|
ulong len = block->length, used = block->used;
|
|
Query_cache_block *pprev = block->pprev,
|
|
*pnext = block->pnext,
|
|
*new_block =(Query_cache_block*) *border;
|
|
char *data = (char*) block->data();
|
|
block->destroy();
|
|
new_block->init(len);
|
|
new_block->type=type;
|
|
new_block->used=used;
|
|
memmove((char*) new_block->data(), data, len - new_block->headers_len());
|
|
relink(block, new_block, next, prev, pnext, pprev);
|
|
new_block->result()->parent(query_block);
|
|
Query_cache_query *query = query_block->query();
|
|
if (query->result() == block)
|
|
query->result(new_block);
|
|
*border += len;
|
|
*before = new_block;
|
|
/* If result writing complete && we have free space in block */
|
|
ulong free_space= new_block->length - new_block->used;
|
|
free_space-= free_space % ALIGN_SIZE(1);
|
|
if (query->result()->type == Query_cache_block::RESULT &&
|
|
new_block->length > new_block->used &&
|
|
*gap + free_space > min_allocation_unit &&
|
|
new_block->length - free_space > min_allocation_unit)
|
|
{
|
|
*border-= free_space;
|
|
*gap+= free_space;
|
|
DBUG_PRINT("qcache",
|
|
("rest of result free space added to gap (%lu)", *gap));
|
|
new_block->length -= free_space;
|
|
}
|
|
BLOCK_UNLOCK_WR(query_block);
|
|
DBUG_PRINT("qcache", ("moved %lu bytes to 0x%lx, new gap at 0x%lx",
|
|
len, (ulong) new_block, (ulong) *border));
|
|
break;
|
|
}
|
|
default:
|
|
DBUG_PRINT("error", ("unexpected block type %d, block 0x%lx",
|
|
(int)block->type, (ulong) block));
|
|
ok = 0;
|
|
}
|
|
DBUG_RETURN(ok);
|
|
}
|
|
|
|
|
|
void Query_cache::relink(Query_cache_block *oblock,
|
|
Query_cache_block *nblock,
|
|
Query_cache_block *next, Query_cache_block *prev,
|
|
Query_cache_block *pnext, Query_cache_block *pprev)
|
|
{
|
|
if (prev == oblock) //check pointer to himself
|
|
{
|
|
nblock->prev = nblock;
|
|
nblock->next = nblock;
|
|
}
|
|
else
|
|
{
|
|
nblock->prev = prev;
|
|
prev->next=nblock;
|
|
}
|
|
if (next != oblock)
|
|
{
|
|
nblock->next = next;
|
|
next->prev=nblock;
|
|
}
|
|
nblock->pprev = pprev; // Physical pointer to himself have only 1 free block
|
|
nblock->pnext = pnext;
|
|
pprev->pnext=nblock;
|
|
pnext->pprev=nblock;
|
|
}
|
|
|
|
|
|
my_bool Query_cache::join_results(ulong join_limit)
|
|
{
|
|
my_bool has_moving = 0;
|
|
DBUG_ENTER("Query_cache::join_results");
|
|
|
|
if (queries_blocks != 0)
|
|
{
|
|
DBUG_ASSERT(query_cache_size > 0);
|
|
Query_cache_block *block = queries_blocks;
|
|
do
|
|
{
|
|
Query_cache_query *header = block->query();
|
|
if (header->result() != 0 &&
|
|
header->result()->type == Query_cache_block::RESULT &&
|
|
header->length() > join_limit)
|
|
{
|
|
Query_cache_block *new_result_block =
|
|
get_free_block(ALIGN_SIZE(header->length()) +
|
|
ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)), 1, 0);
|
|
if (new_result_block != 0)
|
|
{
|
|
has_moving = 1;
|
|
Query_cache_block *first_result = header->result();
|
|
ulong new_len = (header->length() +
|
|
ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
if (new_result_block->length >
|
|
ALIGN_SIZE(new_len) + min_allocation_unit)
|
|
split_block(new_result_block, ALIGN_SIZE(new_len));
|
|
BLOCK_LOCK_WR(block);
|
|
header->result(new_result_block);
|
|
new_result_block->type = Query_cache_block::RESULT;
|
|
new_result_block->n_tables = 0;
|
|
new_result_block->used = new_len;
|
|
|
|
new_result_block->next = new_result_block->prev = new_result_block;
|
|
DBUG_PRINT("qcache", ("new block %lu/%lu (%lu)",
|
|
new_result_block->length,
|
|
new_result_block->used,
|
|
header->length()));
|
|
|
|
Query_cache_result *new_result = new_result_block->result();
|
|
new_result->parent(block);
|
|
uchar *write_to = (uchar*) new_result->data();
|
|
Query_cache_block *result_block = first_result;
|
|
do
|
|
{
|
|
ulong len = (result_block->used - result_block->headers_len() -
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
DBUG_PRINT("loop", ("add block %lu/%lu (%lu)",
|
|
result_block->length,
|
|
result_block->used,
|
|
len));
|
|
memcpy((char *) write_to,
|
|
(char*) result_block->result()->data(),
|
|
len);
|
|
write_to += len;
|
|
Query_cache_block *old_result_block = result_block;
|
|
result_block = result_block->next;
|
|
free_memory_block(old_result_block);
|
|
} while (result_block != first_result);
|
|
BLOCK_UNLOCK_WR(block);
|
|
}
|
|
}
|
|
block = block->next;
|
|
} while ( block != queries_blocks );
|
|
}
|
|
DBUG_RETURN(has_moving);
|
|
}
|
|
|
|
|
|
uint Query_cache::filename_2_table_key (char *key, const char *path,
|
|
uint32 *db_length)
|
|
{
|
|
char tablename[FN_REFLEN+2], *filename, *dbname;
|
|
DBUG_ENTER("Query_cache::filename_2_table_key");
|
|
|
|
/* Safety if filename didn't have a directory name */
|
|
tablename[0]= FN_LIBCHAR;
|
|
tablename[1]= FN_LIBCHAR;
|
|
/* Convert filename to this OS's format in tablename */
|
|
fn_format(tablename + 2, path, "", "", MY_REPLACE_EXT);
|
|
filename= tablename + dirname_length(tablename + 2) + 2;
|
|
/* Find start of databasename */
|
|
for (dbname= filename - 2 ; dbname[-1] != FN_LIBCHAR ; dbname--) ;
|
|
*db_length= (filename - dbname) - 1;
|
|
DBUG_PRINT("qcache", ("table '%-.*s.%s'", *db_length, dbname, filename));
|
|
|
|
DBUG_RETURN((uint) (strmov(strmake(key, dbname, *db_length) + 1,
|
|
filename) -key) + 1);
|
|
}
|
|
|
|
/****************************************************************************
|
|
Functions to be used when debugging
|
|
****************************************************************************/
|
|
|
|
#if defined(DBUG_OFF) && !defined(USE_QUERY_CACHE_INTEGRITY_CHECK)
|
|
|
|
void wreck(uint line, const char *message) { query_cache_size = 0; }
|
|
void bins_dump() {}
|
|
void cache_dump() {}
|
|
void queries_dump() {}
|
|
void tables_dump() {}
|
|
my_bool check_integrity(bool not_locked) { return 0; }
|
|
my_bool in_list(Query_cache_block * root, Query_cache_block * point,
|
|
const char *name) { return 0;}
|
|
my_bool in_blocks(Query_cache_block * point) { return 0; }
|
|
|
|
#else
|
|
|
|
|
|
/*
|
|
Debug method which switch query cache off but left content for
|
|
investigation.
|
|
|
|
SYNOPSIS
|
|
Query_cache::wreck()
|
|
line line of the wreck() call
|
|
message message for logging
|
|
*/
|
|
|
|
void Query_cache::wreck(uint line, const char *message)
|
|
{
|
|
THD *thd=current_thd;
|
|
DBUG_ENTER("Query_cache::wreck");
|
|
query_cache_size = 0;
|
|
if (*message)
|
|
DBUG_PRINT("error", (" %s", message));
|
|
DBUG_PRINT("warning", ("=================================="));
|
|
DBUG_PRINT("warning", ("%5d QUERY CACHE WRECK => DISABLED",line));
|
|
DBUG_PRINT("warning", ("=================================="));
|
|
if (thd)
|
|
thd->killed= THD::KILL_CONNECTION;
|
|
cache_dump();
|
|
/* check_integrity(0); */ /* Can't call it here because of locks */
|
|
bins_dump();
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::bins_dump()
|
|
{
|
|
uint i;
|
|
|
|
if (!initialized || query_cache_size == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("Query Cache not initialized"));
|
|
return;
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("mem_bin_num=%u, mem_bin_steps=%u",
|
|
mem_bin_num, mem_bin_steps));
|
|
DBUG_PRINT("qcache", ("-------------------------"));
|
|
DBUG_PRINT("qcache", (" size idx step"));
|
|
DBUG_PRINT("qcache", ("-------------------------"));
|
|
for (i=0; i < mem_bin_steps; i++)
|
|
{
|
|
DBUG_PRINT("qcache", ("%10lu %3d %10lu", steps[i].size, steps[i].idx,
|
|
steps[i].increment));
|
|
}
|
|
DBUG_PRINT("qcache", ("-------------------------"));
|
|
DBUG_PRINT("qcache", (" size num"));
|
|
DBUG_PRINT("qcache", ("-------------------------"));
|
|
for (i=0; i < mem_bin_num; i++)
|
|
{
|
|
DBUG_PRINT("qcache", ("%10lu %3d 0x%lx", bins[i].size, bins[i].number,
|
|
(ulong)&(bins[i])));
|
|
if (bins[i].free_blocks)
|
|
{
|
|
Query_cache_block *block = bins[i].free_blocks;
|
|
do{
|
|
DBUG_PRINT("qcache", ("\\-- %lu 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx",
|
|
block->length, (ulong)block,
|
|
(ulong)block->next, (ulong)block->prev,
|
|
(ulong)block->pnext, (ulong)block->pprev));
|
|
block = block->next;
|
|
} while ( block != bins[i].free_blocks );
|
|
}
|
|
}
|
|
DBUG_PRINT("qcache", ("-------------------------"));
|
|
}
|
|
|
|
|
|
void Query_cache::cache_dump()
|
|
{
|
|
if (!initialized || query_cache_size == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("Query Cache not initialized"));
|
|
return;
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("-------------------------------------"));
|
|
DBUG_PRINT("qcache", (" length used t nt"));
|
|
DBUG_PRINT("qcache", ("-------------------------------------"));
|
|
Query_cache_block *i = first_block;
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("%10lu %10lu %1d %2d 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx",
|
|
i->length, i->used, (int)i->type,
|
|
i->n_tables, (ulong)i,
|
|
(ulong)i->next, (ulong)i->prev, (ulong)i->pnext,
|
|
(ulong)i->pprev));
|
|
i = i->pnext;
|
|
} while ( i != first_block );
|
|
DBUG_PRINT("qcache", ("-------------------------------------"));
|
|
}
|
|
|
|
|
|
void Query_cache::queries_dump()
|
|
{
|
|
|
|
if (!initialized)
|
|
{
|
|
DBUG_PRINT("qcache", ("Query Cache not initialized"));
|
|
return;
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("------------------"));
|
|
DBUG_PRINT("qcache", (" QUERIES"));
|
|
DBUG_PRINT("qcache", ("------------------"));
|
|
if (queries_blocks != 0)
|
|
{
|
|
Query_cache_block *block = queries_blocks;
|
|
do
|
|
{
|
|
size_t len;
|
|
char *str = (char*) query_cache_query_get_key((uchar*) block, &len, 0);
|
|
len-= QUERY_CACHE_FLAGS_SIZE; // Point at flags
|
|
Query_cache_query_flags flags;
|
|
memcpy(&flags, str+len, QUERY_CACHE_FLAGS_SIZE);
|
|
str[len]= 0; // make zero ending DB name
|
|
DBUG_PRINT("qcache", ("F: %u C: %u L: %lu T: '%s' (%lu) '%s' '%s'",
|
|
flags.client_long_flag,
|
|
flags.character_set_client_num,
|
|
(ulong)flags.limit,
|
|
flags.time_zone->get_name()->ptr(),
|
|
(ulong) len, str, strend(str)+1));
|
|
DBUG_PRINT("qcache", ("-b- 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx", (ulong) block,
|
|
(ulong) block->next, (ulong) block->prev,
|
|
(ulong)block->pnext, (ulong)block->pprev));
|
|
memcpy(str + len, &flags, QUERY_CACHE_FLAGS_SIZE); // restore flags
|
|
for (TABLE_COUNTER_TYPE t= 0; t < block->n_tables; t++)
|
|
{
|
|
Query_cache_table *table= block->table(t)->parent;
|
|
DBUG_PRINT("qcache", ("-t- '%s' '%s'", table->db(), table->table()));
|
|
}
|
|
Query_cache_query *header = block->query();
|
|
if (header->result())
|
|
{
|
|
Query_cache_block *result_block = header->result();
|
|
Query_cache_block *result_beg = result_block;
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("-r- %u %lu/%lu 0x%lx 0x%lx 0x%lx 0x%lx 0x%lx",
|
|
(uint) result_block->type,
|
|
result_block->length, result_block->used,
|
|
(ulong) result_block,
|
|
(ulong) result_block->next,
|
|
(ulong) result_block->prev,
|
|
(ulong) result_block->pnext,
|
|
(ulong) result_block->pprev));
|
|
result_block = result_block->next;
|
|
} while ( result_block != result_beg );
|
|
}
|
|
} while ((block=block->next) != queries_blocks);
|
|
}
|
|
else
|
|
{
|
|
DBUG_PRINT("qcache", ("no queries in list"));
|
|
}
|
|
DBUG_PRINT("qcache", ("------------------"));
|
|
}
|
|
|
|
|
|
void Query_cache::tables_dump()
|
|
{
|
|
if (!initialized || query_cache_size == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("Query Cache not initialized"));
|
|
return;
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("--------------------"));
|
|
DBUG_PRINT("qcache", ("TABLES"));
|
|
DBUG_PRINT("qcache", ("--------------------"));
|
|
if (tables_blocks != 0)
|
|
{
|
|
Query_cache_block *table_block = tables_blocks;
|
|
do
|
|
{
|
|
Query_cache_table *table = table_block->table();
|
|
DBUG_PRINT("qcache", ("'%s' '%s'", table->db(), table->table()));
|
|
table_block = table_block->next;
|
|
} while (table_block != tables_blocks);
|
|
}
|
|
else
|
|
DBUG_PRINT("qcache", ("no tables in list"));
|
|
DBUG_PRINT("qcache", ("--------------------"));
|
|
}
|
|
|
|
|
|
/**
|
|
@brief Checks integrity of the various linked lists
|
|
|
|
@return Error status code
|
|
@retval FALSE Query cache is operational.
|
|
@retval TRUE Query cache is broken.
|
|
*/
|
|
|
|
my_bool Query_cache::check_integrity(bool locked)
|
|
{
|
|
my_bool result = 0;
|
|
uint i;
|
|
DBUG_ENTER("check_integrity");
|
|
|
|
if (!locked)
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
|
|
while (is_flushing())
|
|
pthread_cond_wait(&COND_cache_status_changed,&structure_guard_mutex);
|
|
|
|
if (hash_check(&queries))
|
|
{
|
|
DBUG_PRINT("error", ("queries hash is damaged"));
|
|
result = 1;
|
|
}
|
|
|
|
if (hash_check(&tables))
|
|
{
|
|
DBUG_PRINT("error", ("tables hash is damaged"));
|
|
result = 1;
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("physical address check ..."));
|
|
ulong free=0, used=0;
|
|
Query_cache_block * block = first_block;
|
|
do
|
|
{
|
|
/* When checking at system start, there is no block. */
|
|
if (!block)
|
|
break;
|
|
|
|
DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
|
|
(ulong) block, (uint) block->type));
|
|
// Check allignment
|
|
if ((((long)block) % (long) ALIGN_SIZE(1)) !=
|
|
(((long)first_block) % (long)ALIGN_SIZE(1)))
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx do not aligned by %d", (ulong) block,
|
|
(int) ALIGN_SIZE(1)));
|
|
result = 1;
|
|
}
|
|
// Check memory allocation
|
|
if (block->pnext == first_block) // Is it last block?
|
|
{
|
|
if (((uchar*)block) + block->length !=
|
|
((uchar*)first_block) + query_cache_size)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx, type %u, ended at 0x%lx, but cache ended at 0x%lx",
|
|
(ulong) block, (uint) block->type,
|
|
(ulong) (((uchar*)block) + block->length),
|
|
(ulong) (((uchar*)first_block) + query_cache_size)));
|
|
result = 1;
|
|
}
|
|
}
|
|
else
|
|
if (((uchar*)block) + block->length != ((uchar*)block->pnext))
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx, type %u, ended at 0x%lx, but next block begining at 0x%lx",
|
|
(ulong) block, (uint) block->type,
|
|
(ulong) (((uchar*)block) + block->length),
|
|
(ulong) ((uchar*)block->pnext)));
|
|
}
|
|
if (block->type == Query_cache_block::FREE)
|
|
free+= block->length;
|
|
else
|
|
used+= block->length;
|
|
switch(block->type) {
|
|
case Query_cache_block::FREE:
|
|
{
|
|
Query_cache_memory_bin *bin = *((Query_cache_memory_bin **)
|
|
block->data());
|
|
//is it correct pointer?
|
|
if (((uchar*)bin) < ((uchar*)bins) ||
|
|
((uchar*)bin) >= ((uchar*)first_block))
|
|
{
|
|
DBUG_PRINT("error",
|
|
("free block 0x%lx have bin pointer 0x%lx beyaond of bins array bounds [0x%lx,0x%lx]",
|
|
(ulong) block,
|
|
(ulong) bin,
|
|
(ulong) bins,
|
|
(ulong) first_block));
|
|
result = 1;
|
|
}
|
|
else
|
|
{
|
|
int idx = (((uchar*)bin) - ((uchar*)bins)) /
|
|
sizeof(Query_cache_memory_bin);
|
|
if (in_list(bins[idx].free_blocks, block, "free memory"))
|
|
result = 1;
|
|
}
|
|
break;
|
|
}
|
|
case Query_cache_block::TABLE:
|
|
if (in_list(tables_blocks, block, "tables"))
|
|
result = 1;
|
|
if (in_table_list(block->table(0), block->table(0), "table list root"))
|
|
result = 1;
|
|
break;
|
|
case Query_cache_block::QUERY:
|
|
{
|
|
if (in_list(queries_blocks, block, "query"))
|
|
result = 1;
|
|
for (TABLE_COUNTER_TYPE j=0; j < block->n_tables; j++)
|
|
{
|
|
Query_cache_block_table *block_table = block->table(j);
|
|
Query_cache_block_table *block_table_root =
|
|
(Query_cache_block_table *)
|
|
(((uchar*)block_table->parent) -
|
|
ALIGN_SIZE(sizeof(Query_cache_block_table)));
|
|
|
|
if (in_table_list(block_table, block_table_root, "table list"))
|
|
result = 1;
|
|
}
|
|
break;
|
|
}
|
|
case Query_cache_block::RES_INCOMPLETE:
|
|
// This type of block can be not lincked yet (in multithread environment)
|
|
break;
|
|
case Query_cache_block::RES_BEG:
|
|
case Query_cache_block::RES_CONT:
|
|
case Query_cache_block::RESULT:
|
|
{
|
|
Query_cache_block * query_block = block->result()->parent();
|
|
if (((uchar*)query_block) < ((uchar*)first_block) ||
|
|
((uchar*)query_block) >= (((uchar*)first_block) + query_cache_size))
|
|
{
|
|
DBUG_PRINT("error",
|
|
("result block 0x%lx have query block pointer 0x%lx beyaond of block pool bounds [0x%lx,0x%lx]",
|
|
(ulong) block,
|
|
(ulong) query_block,
|
|
(ulong) first_block,
|
|
(ulong) (((uchar*)first_block) + query_cache_size)));
|
|
result = 1;
|
|
}
|
|
else
|
|
{
|
|
BLOCK_LOCK_RD(query_block);
|
|
if (in_list(queries_blocks, query_block, "query from results"))
|
|
result = 1;
|
|
if (in_list(query_block->query()->result(), block,
|
|
"results"))
|
|
result = 1;
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
DBUG_PRINT("error", ("block 0x%lx have incorrect type %u",
|
|
(long) block, block->type));
|
|
result = 1;
|
|
}
|
|
|
|
block = block->pnext;
|
|
} while (block != first_block);
|
|
|
|
if (used + free != query_cache_size)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("used memory (%lu) + free memory (%lu) != query_cache_size (%lu)",
|
|
used, free, query_cache_size));
|
|
result = 1;
|
|
}
|
|
|
|
if (free != free_memory)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("free memory (%lu) != free_memory (%lu)",
|
|
free, free_memory));
|
|
result = 1;
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("check queries ..."));
|
|
if ((block = queries_blocks))
|
|
{
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
|
|
(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);
|
|
if (((uchar*)block) != val)
|
|
{
|
|
DBUG_PRINT("error", ("block 0x%lx found in queries hash like 0x%lx",
|
|
(ulong) block, (ulong) val));
|
|
}
|
|
if (in_blocks(block))
|
|
result = 1;
|
|
Query_cache_block * results = block->query()->result();
|
|
if (results)
|
|
{
|
|
Query_cache_block * result_block = results;
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
|
|
(ulong) block, (uint) block->type));
|
|
if (in_blocks(result_block))
|
|
result = 1;
|
|
|
|
result_block = result_block->next;
|
|
} while (result_block != results);
|
|
}
|
|
block = block->next;
|
|
} while (block != queries_blocks);
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("check tables ..."));
|
|
if ((block = tables_blocks))
|
|
{
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
|
|
(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);
|
|
if (((uchar*)block) != val)
|
|
{
|
|
DBUG_PRINT("error", ("block 0x%lx found in tables hash like 0x%lx",
|
|
(ulong) block, (ulong) val));
|
|
}
|
|
|
|
if (in_blocks(block))
|
|
result = 1;
|
|
block=block->next;
|
|
} while (block != tables_blocks);
|
|
}
|
|
|
|
DBUG_PRINT("qcache", ("check free blocks"));
|
|
for (i = 0; i < mem_bin_num; i++)
|
|
{
|
|
if ((block = bins[i].free_blocks))
|
|
{
|
|
uint count = 0;
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("block 0x%lx, type %u...",
|
|
(ulong) block, (uint) block->type));
|
|
if (in_blocks(block))
|
|
result = 1;
|
|
|
|
count++;
|
|
block=block->next;
|
|
} while (block != bins[i].free_blocks);
|
|
if (count != bins[i].number)
|
|
{
|
|
DBUG_PRINT("error", ("bins[%d].number= %d, but bin have %d blocks",
|
|
i, bins[i].number, count));
|
|
result = 1;
|
|
}
|
|
}
|
|
}
|
|
DBUG_ASSERT(result == 0);
|
|
if (!locked)
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_RETURN(result);
|
|
}
|
|
|
|
|
|
my_bool Query_cache::in_blocks(Query_cache_block * point)
|
|
{
|
|
my_bool result = 0;
|
|
Query_cache_block *block = point;
|
|
//back
|
|
do
|
|
{
|
|
if (block->pprev->pnext != block)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx in physical list is incorrect linked, prev block 0x%lx refered as next to 0x%lx (check from 0x%lx)",
|
|
(ulong) block, (ulong) block->pprev,
|
|
(ulong) block->pprev->pnext,
|
|
(ulong) point));
|
|
//back trace
|
|
for (; block != point; block = block->pnext)
|
|
DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
|
|
result = 1;
|
|
goto err1;
|
|
}
|
|
block = block->pprev;
|
|
} while (block != first_block && block != point);
|
|
if (block != first_block)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx (0x%lx<-->0x%lx) not owned by pysical list",
|
|
(ulong) block, (ulong) block->pprev, (ulong )block->pnext));
|
|
return 1;
|
|
}
|
|
|
|
err1:
|
|
//forward
|
|
block = point;
|
|
do
|
|
{
|
|
if (block->pnext->pprev != block)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx in physicel list is incorrect linked, next block 0x%lx refered as prev to 0x%lx (check from 0x%lx)",
|
|
(ulong) block, (ulong) block->pnext,
|
|
(ulong) block->pnext->pprev,
|
|
(ulong) point));
|
|
//back trace
|
|
for (; block != point; block = block->pprev)
|
|
DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
|
|
result = 1;
|
|
goto err2;
|
|
}
|
|
block = block->pnext;
|
|
} while (block != first_block);
|
|
err2:
|
|
return result;
|
|
}
|
|
|
|
|
|
my_bool Query_cache::in_list(Query_cache_block * root,
|
|
Query_cache_block * point,
|
|
const char *name)
|
|
{
|
|
my_bool result = 0;
|
|
Query_cache_block *block = point;
|
|
//back
|
|
do
|
|
{
|
|
if (block->prev->next != block)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx in list '%s' 0x%lx is incorrect linked, prev block 0x%lx refered as next to 0x%lx (check from 0x%lx)",
|
|
(ulong) block, name, (ulong) root, (ulong) block->prev,
|
|
(ulong) block->prev->next,
|
|
(ulong) point));
|
|
//back trace
|
|
for (; block != point; block = block->next)
|
|
DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
|
|
result = 1;
|
|
goto err1;
|
|
}
|
|
block = block->prev;
|
|
} while (block != root && block != point);
|
|
if (block != root)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx (0x%lx<-->0x%lx) not owned by list '%s' 0x%lx",
|
|
(ulong) block,
|
|
(ulong) block->prev, (ulong) block->next,
|
|
name, (ulong) root));
|
|
return 1;
|
|
}
|
|
err1:
|
|
// forward
|
|
block = point;
|
|
do
|
|
{
|
|
if (block->next->prev != block)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("block 0x%lx in list '%s' 0x%lx is incorrect linked, next block 0x%lx refered as prev to 0x%lx (check from 0x%lx)",
|
|
(ulong) block, name, (ulong) root, (ulong) block->next,
|
|
(ulong) block->next->prev,
|
|
(ulong) point));
|
|
//back trace
|
|
for (; block != point; block = block->prev)
|
|
DBUG_PRINT("error", ("back trace 0x%lx", (ulong) block));
|
|
result = 1;
|
|
goto err2;
|
|
}
|
|
block = block->next;
|
|
} while (block != root);
|
|
err2:
|
|
return result;
|
|
}
|
|
|
|
void dump_node(Query_cache_block_table * node,
|
|
const char * call, const char * descr)
|
|
{
|
|
DBUG_PRINT("qcache", ("%s: %s: node: 0x%lx", call, descr, (ulong) node));
|
|
DBUG_PRINT("qcache", ("%s: %s: node block: 0x%lx",
|
|
call, descr, (ulong) node->block()));
|
|
DBUG_PRINT("qcache", ("%s: %s: next: 0x%lx", call, descr,
|
|
(ulong) node->next));
|
|
DBUG_PRINT("qcache", ("%s: %s: prev: 0x%lx", call, descr,
|
|
(ulong) node->prev));
|
|
}
|
|
|
|
my_bool Query_cache::in_table_list(Query_cache_block_table * root,
|
|
Query_cache_block_table * point,
|
|
const char *name)
|
|
{
|
|
my_bool result = 0;
|
|
Query_cache_block_table *table = point;
|
|
dump_node(root, name, "parameter root");
|
|
//back
|
|
do
|
|
{
|
|
dump_node(table, name, "list element << ");
|
|
if (table->prev->next != table)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("table 0x%lx(0x%lx) in list '%s' 0x%lx(0x%lx) is incorrect linked, prev table 0x%lx(0x%lx) refered as next to 0x%lx(0x%lx) (check from 0x%lx(0x%lx))",
|
|
(ulong) table, (ulong) table->block(), name,
|
|
(ulong) root, (ulong) root->block(),
|
|
(ulong) table->prev, (ulong) table->prev->block(),
|
|
(ulong) table->prev->next,
|
|
(ulong) table->prev->next->block(),
|
|
(ulong) point, (ulong) point->block()));
|
|
//back trace
|
|
for (; table != point; table = table->next)
|
|
DBUG_PRINT("error", ("back trace 0x%lx(0x%lx)",
|
|
(ulong) table, (ulong) table->block()));
|
|
result = 1;
|
|
goto err1;
|
|
}
|
|
table = table->prev;
|
|
} while (table != root && table != point);
|
|
if (table != root)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("table 0x%lx(0x%lx) (0x%lx(0x%lx)<-->0x%lx(0x%lx)) not owned by list '%s' 0x%lx(0x%lx)",
|
|
(ulong) table, (ulong) table->block(),
|
|
(ulong) table->prev, (ulong) table->prev->block(),
|
|
(ulong) table->next, (ulong) table->next->block(),
|
|
name, (ulong) root, (ulong) root->block()));
|
|
return 1;
|
|
}
|
|
err1:
|
|
// forward
|
|
table = point;
|
|
do
|
|
{
|
|
dump_node(table, name, "list element >> ");
|
|
if (table->next->prev != table)
|
|
{
|
|
DBUG_PRINT("error",
|
|
("table 0x%lx(0x%lx) in list '%s' 0x%lx(0x%lx) is incorrect linked, next table 0x%lx(0x%lx) refered as prev to 0x%lx(0x%lx) (check from 0x%lx(0x%lx))",
|
|
(ulong) table, (ulong) table->block(),
|
|
name, (ulong) root, (ulong) root->block(),
|
|
(ulong) table->next, (ulong) table->next->block(),
|
|
(ulong) table->next->prev,
|
|
(ulong) table->next->prev->block(),
|
|
(ulong) point, (ulong) point->block()));
|
|
//back trace
|
|
for (; table != point; table = table->prev)
|
|
DBUG_PRINT("error", ("back trace 0x%lx(0x%lx)",
|
|
(ulong) table, (ulong) table->block()));
|
|
result = 1;
|
|
goto err2;
|
|
}
|
|
table = table->next;
|
|
} while (table != root);
|
|
err2:
|
|
return result;
|
|
}
|
|
|
|
#endif /* DBUG_OFF */
|
|
|
|
#endif /*HAVE_QUERY_CACHE*/
|