mirror of
https://github.com/MariaDB/server.git
synced 2025-01-16 12:02:42 +01:00
cfab46f546
Fixed memory leak on shutdown (Affects the embedded version & MyODBC) client/client_priv.h: Update copyright client/completion_hash.cc: Update copyright client/completion_hash.h: Update copyright client/connect_test.c: Update copyright client/errmsg.c: Update copyright client/get_password.c: Update copyright client/insert_test.c: Update copyright client/list_test.c: Update copyright client/my_readline.h: Update copyright client/mysql.cc: Update copyright client/mysqladmin.c: Update copyright client/mysqlbinlog.cc: Update copyright client/mysqlcheck.c: Update copyright client/mysqldump.c: Update copyright client/mysqlimport.c: Update copyright client/mysqlmanager-pwgen.c: Update copyright client/mysqlmanagerc.c: Update copyright client/mysqlshow.c: Update copyright client/mysqltest.c: Update copyright client/password.c: Update copyright client/readline.cc: Update copyright client/select_test.c: Update copyright client/showdb_test.c: Update copyright client/sql_string.cc: Update copyright client/sql_string.h: Update copyright client/ssl_test.c: Update copyright client/thimble.cc: Update copyright client/thread_test.c: Update copyright div/deadlock_test.c: Update copyright extra/comp_err.c: Update copyright extra/my_print_defaults.c: Update copyright extra/perror.c: Update copyright extra/replace.c: Update copyright extra/resolve_stack_dump.c: Update copyright extra/resolveip.c: Update copyright fs/database.c: Update copyright fs/libmysqlfs.c: Update copyright fs/mysqlcorbafs.c: Update copyright fs/mysqlcorbafs.h: Update copyright fs/mysqlcorbafs_test.c: Update copyright heap/_check.c: Update copyright heap/_rectest.c: Update copyright heap/heapdef.h: Update copyright heap/hp_block.c: Update copyright heap/hp_clear.c: Update copyright heap/hp_close.c: Update copyright heap/hp_create.c: Update copyright heap/hp_delete.c: Update copyright heap/hp_extra.c: Update copyright heap/hp_hash.c: Update copyright heap/hp_info.c: Update copyright heap/hp_open.c: Update copyright heap/hp_panic.c: Update copyright heap/hp_rename.c: Update copyright heap/hp_rfirst.c: Update copyright heap/hp_rkey.c: Update copyright heap/hp_rlast.c: Update copyright heap/hp_rnext.c: Update copyright heap/hp_rprev.c: Update copyright heap/hp_rrnd.c: Update copyright heap/hp_rsame.c: Update copyright heap/hp_scan.c: Update copyright heap/hp_static.c: Update copyright heap/hp_test1.c: Update copyright heap/hp_test2.c: Update copyright heap/hp_update.c: Update copyright heap/hp_write.c: Update copyright include/config-win.h: Update copyright include/dbug.h: Update copyright include/errmsg.h: Update copyright include/ft_global.h: Update copyright include/getopt.h: Update copyright include/hash.h: Update copyright include/heap.h: Update copyright include/m_ctype.h: Update copyright include/m_string.h: Update copyright include/md5.h: Update copyright include/merge.h: Update copyright include/my_alarm.h: Update copyright include/my_base.h: Update copyright include/my_bitmap.h: Update copyright include/my_dir.h: Update copyright include/my_global.h: Update copyright include/my_list.h: Update copyright include/my_net.h: Update copyright include/my_no_pthread.h: Update copyright include/my_nosys.h: Update copyright include/my_pthread.h: Update copyright include/my_sys.h: Update copyright include/my_tree.h: Update copyright include/myisam.h: Update copyright include/myisammrg.h: Update copyright include/myisampack.h: Update copyright include/mysql.h: Update copyright include/mysql_com.h: Update copyright include/mysql_embed.h: Update copyright include/mysqld_error.h: Update copyright include/mysys_err.h: Update copyright include/nisam.h: Update copyright include/queues.h: Update copyright include/raid.h: Update copyright include/sslopt-case.h: Update copyright include/sslopt-longopts.h: Update copyright include/sslopt-usage.h: Update copyright include/sslopt-vars.h: Update copyright include/t_ctype.h: Update copyright include/thr_alarm.h: Update copyright include/thr_lock.h: Update copyright include/violite.h: Update copyright isam/_cache.c: Update copyright isam/_dbug.c: Update copyright isam/_key.c: Update copyright isam/_locking.c: Update copyright isam/_packrec.c: Update copyright isam/_page.c: Update copyright isam/_search.c: Update copyright isam/_statrec.c: Update copyright isam/changed.c: Update copyright isam/close.c: Update copyright isam/create.c: Update copyright isam/delete.c: Update copyright isam/extra.c: Update copyright isam/info.c: Update copyright isam/isamchk.c: Update copyright isam/isamdef.h: Update copyright isam/log.c: Update copyright isam/open.c: Update copyright isam/panic.c: Update copyright isam/range.c: Update copyright isam/rfirst.c: Update copyright isam/rkey.c: Update copyright isam/rlast.c: Update copyright isam/rnext.c: Update copyright isam/rprev.c: Update copyright isam/rrnd.c: Update copyright isam/rsame.c: Update copyright isam/rsamepos.c: Update copyright isam/sort.c: Update copyright isam/static.c: Update copyright isam/test1.c: Update copyright isam/test2.c: Update copyright isam/test3.c: Update copyright isam/update.c: Update copyright isam/write.c: Update copyright libmysql/conf_to_src.c: Update copyright libmysql/dll.c: Update copyright libmysql/errmsg.c: Update copyright libmysql/get_password.c: Update copyright libmysql/libmysql.c: Update copyright libmysql/manager.c: Update copyright libmysql/net.c: Update copyright libmysql/password.c: Update copyright libmysqld/lib_sql.cc: Update copyright libmysqld/lib_vio.c: Update copyright libmysqld/libmysqld.c: Update copyright merge/mrg_close.c: Update copyright merge/mrg_create.c: Update copyright merge/mrg_def.h: Update copyright merge/mrg_delete.c: Update copyright merge/mrg_extra.c: Update copyright merge/mrg_info.c: Update copyright merge/mrg_locking.c: Update copyright merge/mrg_open.c: Update copyright merge/mrg_panic.c: Update copyright merge/mrg_rrnd.c: Update copyright merge/mrg_rsame.c: Update copyright merge/mrg_static.c: Update copyright merge/mrg_update.c: Update copyright myisam/ft_boolean_search.c: Update copyright myisam/ft_dump.c: Update copyright myisam/ft_eval.h: Update copyright myisam/ft_static.c: Update copyright myisam/ft_stem.c: Update copyright myisam/ft_stopwords.c: Update copyright myisam/ft_test1.h: Update copyright myisam/mi_cache.c: Update copyright myisam/mi_changed.c: Update copyright myisam/mi_check.c: Update copyright myisam/mi_checksum.c: Update copyright myisam/mi_close.c: Update copyright myisam/mi_create.c: Update copyright myisam/mi_dbug.c: Update copyright myisam/mi_delete.c: Update copyright myisam/mi_delete_all.c: Update copyright myisam/mi_delete_table.c: Update copyright myisam/mi_dynrec.c: Update copyright myisam/mi_extra.c: Update copyright myisam/mi_info.c: Update copyright myisam/mi_key.c: Update copyright myisam/mi_locking.c: Update copyright myisam/mi_log.c: Update copyright myisam/mi_open.c: Update copyright myisam/mi_packrec.c: Update copyright myisam/mi_page.c: Update copyright myisam/mi_panic.c: Update copyright myisam/mi_range.c: Update copyright myisam/mi_rename.c: Update copyright myisam/mi_rfirst.c: Update copyright myisam/mi_rlast.c: Update copyright myisam/mi_rnext_same.c: Update copyright myisam/mi_rrnd.c: Update copyright myisam/mi_rsame.c: Update copyright myisam/mi_rsamepos.c: Update copyright myisam/mi_scan.c: Update copyright myisam/mi_search.c: Update copyright myisam/mi_static.c: Update copyright myisam/mi_statrec.c: Update copyright myisam/mi_test1.c: Update copyright myisam/mi_test2.c: Update copyright myisam/mi_test3.c: Update copyright myisam/mi_unique.c: Update copyright myisam/mi_update.c: Update copyright myisam/mi_write.c: Update copyright myisam/myisamchk.c: Update copyright myisam/myisampack.c: Update copyright myisammrg/myrg_close.c: Update copyright myisammrg/myrg_create.c: Update copyright myisammrg/myrg_def.h: Update copyright myisammrg/myrg_delete.c: Update copyright myisammrg/myrg_locking.c: Update copyright myisammrg/myrg_open.c: Update copyright myisammrg/myrg_panic.c: Update copyright myisammrg/myrg_rsame.c: Update copyright myisammrg/myrg_static.c: Update copyright myisammrg/myrg_update.c: Update copyright myisammrg/myrg_write.c: Update copyright mysql-test/r/gcc296.result: Update of benchmark results mysql-test/r/innodb.result: Update of benchmark results mysql-test/r/join_outer.result: Update of benchmark results mysql-test/r/myisam.result: Update of benchmark results mysys/array.c: Update copyright mysys/charset.c: Fix for restart of character sets mysys/checksum.c: Update copyright mysys/default.c: Update copyright mysys/errors.c: Update copyright mysys/getopt.c: Cleanup mysys/getvar.c: Update copyright mysys/hash.c: Update copyright mysys/list.c: Update copyright mysys/make-conf.c: Update copyright mysys/md5.c: Update copyright mysys/mf_brkhant.c: Update copyright mysys/mf_cache.c: Update copyright mysys/mf_casecnv.c: Update copyright mysys/mf_dirname.c: Update copyright mysys/mf_fn_ext.c: Update copyright mysys/mf_format.c: Update copyright mysys/mf_getdate.c: Update copyright mysys/mf_iocache.c: Update copyright mysys/mf_iocache2.c: Update copyright mysys/mf_keycache.c: Update copyright mysys/mf_loadpath.c: Update copyright mysys/mf_pack.c: Update copyright mysys/mf_path.c: Update copyright mysys/mf_qsort.c: Update copyright mysys/mf_qsort2.c: Update copyright mysys/mf_radix.c: Update copyright mysys/mf_same.c: Update copyright mysys/mf_sleep.c: Update copyright mysys/mf_sort.c: Update copyright mysys/mf_soundex.c: Update copyright mysys/mf_stripp.c: Update copyright mysys/mf_tempfile.c: Update copyright mysys/mf_unixpath.c: Update copyright mysys/mf_util.c: Update copyright mysys/mf_wcomp.c: Update copyright mysys/mf_wfile.c: Update copyright mysys/mulalloc.c: Update copyright mysys/my_alarm.c: Update copyright mysys/my_alloc.c: Update copyright mysys/my_append.c: Update copyright mysys/my_bit.c: Update copyright mysys/my_bitmap.c: Update copyright mysys/my_chsize.c: Update copyright mysys/my_clock.c: Update copyright mysys/my_compress.c: Update copyright mysys/my_copy.c: Update copyright mysys/my_create.c: Update copyright mysys/my_delete.c: Update copyright mysys/my_div.c: Update copyright mysys/my_dup.c: Update copyright mysys/my_error.c: Update copyright mysys/my_fopen.c: Update copyright mysys/my_fstream.c: Update copyright mysys/my_getwd.c: Update copyright mysys/my_init.c: Free 'once_alloc' memory at shutdown. mysys/my_lib.c: Update copyright mysys/my_lock.c: Update copyright mysys/my_lockmem.c: Update copyright mysys/my_lread.c: Update copyright mysys/my_lwrite.c: Update copyright mysys/my_malloc.c: Update copyright mysys/my_messnc.c: Update copyright mysys/my_mkdir.c: Update copyright mysys/my_net.c: Update copyright mysys/my_once.c: Update copyright mysys/my_open.c: Update copyright mysys/my_pread.c: Update copyright mysys/my_pthread.c: Update copyright mysys/my_quick.c: Update copyright mysys/my_read.c: Update copyright mysys/my_realloc.c: Update copyright mysys/my_redel.c: Update copyright mysys/my_rename.c: Update copyright mysys/my_seek.c: Update copyright mysys/my_static.c: Update copyright mysys/my_static.h: Update copyright mysys/my_symlink.c: Update copyright mysys/my_symlink2.c: Update copyright mysys/my_tempnam.c: Update copyright mysys/my_thr_init.c: Update copyright mysys/my_vsnprintf.c: Update copyright mysys/my_wincond.c: Update copyright mysys/my_winthread.c: Update copyright mysys/my_write.c: Update copyright mysys/mysys_priv.h: Update copyright mysys/ptr_cmp.c: Update copyright mysys/queues.c: Update copyright mysys/raid.cc: Update copyright mysys/safemalloc.c: Update copyright mysys/string.c: Update copyright mysys/test_charset.c: Update copyright mysys/test_dir.c: Update copyright mysys/test_fn.c: Update copyright mysys/testhash.c: Update copyright mysys/thr_alarm.c: Update copyright mysys/thr_lock.c: Update copyright mysys/thr_mutex.c: Update copyright mysys/thr_rwlock.c: Update copyright mysys/tree.c: Update copyright mysys/typelib.c: Update copyright pstack/debug.c: Update copyright pstack/debug.h: Update copyright pstack/demangle.h: Update copyright pstack/ieee.c: Update copyright pstack/ieee.h: Update copyright pstack/pstack.c: Update copyright readline/bind.c: Cleanup empty lines readline/complete.c: Cleanup empty lines readline/display.c: Cleanup empty lines readline/funmap.c: Cleanup empty lines readline/histexpand.c: Cleanup empty lines readline/histfile.c: Cleanup empty lines readline/history.c: Cleanup empty lines readline/history.h: Cleanup empty lines readline/input.c: Cleanup empty lines readline/kill.c: Cleanup empty lines readline/readline.c: Cleanup empty lines readline/readline.h: Cleanup empty lines readline/vi_mode.c: Cleanup empty lines sql/cache_manager.cc: Update copyright sql/cache_manager.h: Update copyright sql/convert.cc: Update copyright sql/custom_conf.h: Update copyright sql/derror.cc: Update copyright sql/field.cc: Update copyright sql/field.h: Update copyright sql/field_conv.cc: Update copyright sql/filesort.cc: Update copyright sql/frm_crypt.cc: Update copyright sql/ha_berkeley.cc: Update copyright sql/ha_heap.cc: Update copyright sql/ha_heap.h: Update copyright sql/ha_innobase.cc: Update copyright sql/ha_isam.cc: Update copyright sql/ha_isam.h: Update copyright sql/ha_isammrg.cc: Update copyright sql/ha_isammrg.h: Update copyright sql/ha_myisam.cc: Update copyright sql/handler.cc: Update copyright sql/hash_filo.cc: Update copyright sql/hash_filo.h: Update copyright sql/hostname.cc: Update copyright sql/init.cc: Update copyright sql/item.cc: Update copyright sql/item.h: Update copyright sql/item_buff.cc: Update copyright sql/item_cmpfunc.cc: Update copyright sql/item_cmpfunc.h: Update copyright sql/item_create.cc: Update copyright sql/item_create.h: Update copyright sql/item_func.cc: Update copyright sql/item_strfunc.cc: Update copyright sql/item_sum.cc: Update copyright sql/item_sum.h: Update copyright sql/item_timefunc.cc: Update copyright sql/item_timefunc.h: Update copyright sql/item_uniq.cc: Update copyright sql/item_uniq.h: Update copyright sql/key.cc: Update copyright sql/lex_symbol.h: Update copyright sql/lock.cc: Update copyright sql/log.cc: Update copyright sql/log_event.cc: Update copyright sql/log_event.h: Update copyright sql/matherr.c: Update copyright sql/mf_iocache.cc: Update copyright sql/mini_client.cc: Update copyright sql/mini_client.h: Update copyright sql/my_lock.c: Update copyright sql/mysqld.cc: Update copyright sql/net_pkg.cc: Update copyright sql/net_serv.cc: Update copyright sql/opt_sum.cc: Update copyright sql/password.c: Update copyright sql/procedure.cc: Update copyright sql/procedure.h: Update copyright sql/records.cc: Update copyright sql/repl_failsafe.cc: Update copyright sql/slave.cc: Update copyright sql/slave.h: Update copyright sql/sql_acl.cc: Update copyright sql/sql_acl.h: Update copyright sql/sql_analyse.cc: Update copyright sql/sql_analyse.h: Update copyright sql/sql_base.cc: Update copyright sql/sql_cache.cc: Update copyright sql/sql_class.cc: Update copyright sql/sql_class.h: Update copyright sql/sql_crypt.cc: Update copyright sql/sql_crypt.h: Update copyright sql/sql_db.cc: Update copyright sql/sql_delete.cc: Update copyright sql/sql_handler.cc: Update copyright sql/sql_insert.cc: Update copyright sql/sql_lex.cc: Update copyright sql/sql_lex.h: Update copyright sql/sql_list.cc: Update copyright sql/sql_list.h: Update copyright sql/sql_load.cc: Update copyright sql/sql_map.cc: Update copyright sql/sql_map.h: Update copyright sql/sql_parse.cc: Update copyright sql/sql_rename.cc: Update copyright sql/sql_repl.cc: Update copyright sql/sql_select.h: Update copyright sql/sql_string.cc: Update copyright sql/sql_string.h: Update copyright sql/sql_table.cc: Update copyright sql/sql_test.cc: Update copyright sql/sql_udf.cc: Update copyright sql/sql_udf.h: Update copyright sql/stacktrace.c: Update copyright sql/structs.h: Update copyright sql/table.cc: Update copyright sql/table.h: Update copyright sql/thr_malloc.cc: Update copyright sql/time.cc: Update copyright sql/udf_example.cc: Update copyright sql/uniques.cc: Update copyright sql/unireg.cc: Update copyright sql/unireg.h: Update copyright strings/atof.c: Update copyright strings/bchange.c: Update copyright strings/bcmp.c: Update copyright strings/bcopy-duff.c: Update copyright strings/bfill.c: Update copyright strings/bmove.c: Update copyright strings/bmove512.c: Update copyright strings/bmove_upp.c: Update copyright strings/bzero.c: Update copyright strings/conf_to_src.c: Update copyright strings/ctype-big5.c: Update copyright strings/ctype-czech.c: Update copyright strings/ctype-euc_kr.c: Update copyright strings/ctype-gb2312.c: Update copyright strings/ctype-gbk.c: Update copyright strings/ctype-latin1_de.c: Update copyright strings/ctype-sjis.c: Update copyright strings/ctype-tis620.c: Update copyright strings/ctype-ujis.c: Update copyright strings/ctype.c: Update copyright strings/do_ctype.c: Update copyright strings/int2str.c: Update copyright strings/is_prefix.c: Update copyright strings/llstr.c: Update copyright strings/longlong2str.c: Update copyright strings/memcmp.c: Update copyright strings/memcpy.c: Update copyright strings/memset.c: Update copyright strings/r_strinstr.c: Update copyright strings/str2int.c: Update copyright strings/str_test.c: Update copyright strings/strappend.c: Update copyright strings/strcat.c: Update copyright strings/strcend.c: Update copyright strings/strchr.c: Update copyright strings/strcmp.c: Update copyright strings/strcont.c: Update copyright strings/strend.c: Update copyright strings/strfill.c: Update copyright strings/strings-not-used.h: Update copyright strings/strinstr.c: Update copyright strings/strlen.c: Update copyright strings/strmake.c: Update copyright strings/strmov.c: Update copyright strings/strnlen.c: Update copyright strings/strnmov.c: Update copyright strings/strrchr.c: Update copyright strings/strstr.c: Update copyright strings/strto.c: Update copyright strings/strtol.c: Update copyright strings/strtoll.c: Update copyright strings/strtoul.c: Update copyright strings/strtoull.c: Update copyright strings/strxmov.c: Update copyright strings/strxnmov.c: Update copyright strings/t_ctype.h: Update copyright strings/udiv.c: Update copyright tools/mysqlmanager.c: Update copyright vio/test-ssl.c: Update copyright vio/test-sslclient.c: Update copyright vio/test-sslserver.c: Update copyright vio/vio.c: Update copyright vio/viosocket.c: Update copyright vio/viossl.c: Update copyright vio/viosslfactories.c: Update copyright vio/viotest-ssl.c: Update copyright
2832 lines
85 KiB
C++
2832 lines
85 KiB
C++
/* Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult 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; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
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).
|
|
|
|
6. 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.
|
|
*/
|
|
|
|
#include "mysql_priv.h"
|
|
#include <m_ctype.h>
|
|
#include <my_dir.h>
|
|
#include <hash.h>
|
|
#include "sql_acl.h"
|
|
#include "ha_myisammrg.h"
|
|
#ifndef MASTER
|
|
#include "../srclib/myisammrg/myrg_def.h"
|
|
#else
|
|
#include "../myisammrg/myrg_def.h"
|
|
#endif
|
|
#include <assert.h>
|
|
|
|
#if defined(EXTRA_DEBUG) && !defined(DBUG_OFF)
|
|
#define MUTEX_LOCK(M) { DBUG_PRINT("lock", ("mutex lock 0x%lx", (ulong)(M))); \
|
|
pthread_mutex_lock(M);}
|
|
#define SEM_LOCK(M) { int val = 0; sem_getvalue (M, &val); \
|
|
DBUG_PRINT("lock", ("sem lock 0x%lx (%d)", (ulong)(M), val)); \
|
|
sem_wait(M); DBUG_PRINT("lock", ("sem lock ok")); }
|
|
#define MUTEX_UNLOCK(M) {DBUG_PRINT("lock", ("mutex unlock 0x%lx",\
|
|
(ulong)(M))); pthread_mutex_unlock(M);}
|
|
#define SEM_UNLOCK(M) {DBUG_PRINT("lock", ("sem unlock 0x%lx", (ulong)(M))); \
|
|
sem_post(M); DBUG_PRINT("lock", ("sem unlock ok")); }
|
|
#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)->queries_dump();(C)->tables_dump();})
|
|
#else
|
|
#define MUTEX_LOCK(M) pthread_mutex_lock(M)
|
|
#define SEM_LOCK(M) sem_wait(M)
|
|
#define MUTEX_UNLOCK(M) pthread_mutex_unlock(M)
|
|
#define SEM_UNLOCK(M) sem_post(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
|
|
|
|
/*****************************************************************************
|
|
Query_cache_block_table method(s)
|
|
*****************************************************************************/
|
|
|
|
inline Query_cache_block * Query_cache_block_table::block()
|
|
{
|
|
return (Query_cache_block *)(((byte*)this) -
|
|
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", (ulong) this));
|
|
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 gptr Query_cache_block::data(void)
|
|
{
|
|
return (gptr)( ((byte*)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 *)
|
|
(((byte*)this)+ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
n*sizeof(Query_cache_block_table)));
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* Query_cache_table method(s)
|
|
*****************************************************************************/
|
|
|
|
extern "C"
|
|
{
|
|
byte *query_cache_table_get_key(const byte *record, uint *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 (((byte *) table_block->data()) +
|
|
ALIGN_SIZE(sizeof(Query_cache_table)));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Query_cache_query methods
|
|
*****************************************************************************/
|
|
|
|
void Query_cache_query::init_n_lock()
|
|
{
|
|
DBUG_ENTER("Query_cache_query::init_n_lock");
|
|
res=0; wri = 0; len = 0;
|
|
sem_init(&lock, 0, 1);
|
|
pthread_mutex_init(&clients_guard,MY_MUTEX_INIT_FAST);
|
|
clients = 0;
|
|
lock_writing();
|
|
DBUG_PRINT("qcache", ("inited & locked query for block 0x%lx",
|
|
((byte*) 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");
|
|
/*
|
|
The following call is not needed on system where one can destroy an
|
|
active semaphore
|
|
*/
|
|
this->unlock_writing();
|
|
DBUG_PRINT("qcache", ("destroyed & unlocked query for block 0x%lx",
|
|
((byte*)this)-ALIGN_SIZE(sizeof(Query_cache_block))));
|
|
sem_destroy(&lock);
|
|
pthread_mutex_destroy(&clients_guard);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*
|
|
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.
|
|
*/
|
|
|
|
void Query_cache_query::lock_writing()
|
|
{
|
|
SEM_LOCK(&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 (sem_trywait(&lock)!=0 || clients != 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("can't lock mutex"));
|
|
DBUG_RETURN(0);
|
|
}
|
|
DBUG_PRINT("qcache", ("mutex 'lock' 0x%lx locked", (ulong) &lock));
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
|
|
void Query_cache_query::lock_reading()
|
|
{
|
|
MUTEX_LOCK(&clients_guard);
|
|
if (!clients++)
|
|
SEM_LOCK(&lock);
|
|
MUTEX_UNLOCK(&clients_guard);
|
|
}
|
|
|
|
|
|
void Query_cache_query::unlock_writing()
|
|
{
|
|
SEM_UNLOCK(&lock);
|
|
}
|
|
|
|
|
|
void Query_cache_query::unlock_reading()
|
|
{
|
|
MUTEX_LOCK(&clients_guard);
|
|
if (--clients == 0)
|
|
SEM_UNLOCK(&lock);
|
|
MUTEX_UNLOCK(&clients_guard);
|
|
}
|
|
|
|
extern "C"
|
|
{
|
|
byte *query_cache_query_get_key(const byte *record, uint *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 (((byte *) query_block->data()) +
|
|
ALIGN_SIZE(sizeof(Query_cache_query)));
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Functions to store things into the query cache
|
|
*****************************************************************************/
|
|
|
|
void query_cache_insert(NET *net, const char *packet, ulong length)
|
|
{
|
|
DBUG_ENTER("query_cache_insert");
|
|
|
|
#ifndef DBUG_OFF
|
|
// Check if we have called query_cache.wreck() (which disables the cache)
|
|
if (query_cache.query_cache_size == 0)
|
|
DBUG_VOID_RETURN;
|
|
#endif
|
|
|
|
// Quick check on unlocked structure
|
|
if (net->query_cache_query != 0)
|
|
{
|
|
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
|
Query_cache_block *query_block = ((Query_cache_block*)
|
|
net->query_cache_query);
|
|
if (query_block)
|
|
{
|
|
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(&query_cache.structure_guard_mutex) will be
|
|
done by query_cache.append_result_data if success (if not we need
|
|
query_cache.structure_guard_mutex locked to free query)
|
|
*/
|
|
if (!query_cache.append_result_data(&result, length, (gptr) packet,
|
|
query_block))
|
|
{
|
|
query_cache.refused++;
|
|
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);
|
|
BLOCK_UNLOCK_WR(query_block);
|
|
}
|
|
else
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void query_cache_abort(NET *net)
|
|
{
|
|
DBUG_ENTER("query_cache_abort");
|
|
|
|
#ifndef DBUG_OFF
|
|
// Check if we have called query_cache.wreck() (which disables the cache)
|
|
if (query_cache.query_cache_size == 0)
|
|
DBUG_VOID_RETURN;
|
|
#endif
|
|
if (net->query_cache_query != 0) // Quick check on unlocked structure
|
|
{
|
|
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
|
Query_cache_block *query_block = ((Query_cache_block*)
|
|
net->query_cache_query);
|
|
if (query_block) // Test if changed by other thread
|
|
{
|
|
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;
|
|
}
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void query_cache_end_of_result(NET *net)
|
|
{
|
|
DBUG_ENTER("query_cache_end_of_result");
|
|
|
|
#ifndef DBUG_OFF
|
|
// Check if we have called query_cache.wreck() (which disables the cache)
|
|
if (query_cache.query_cache_size == 0) DBUG_VOID_RETURN;
|
|
#endif
|
|
|
|
if (net->query_cache_query != 0) // Quick check on unlocked structure
|
|
{
|
|
STRUCT_LOCK(&query_cache.structure_guard_mutex);
|
|
Query_cache_block *query_block = ((Query_cache_block*)
|
|
net->query_cache_query);
|
|
if (query_block)
|
|
{
|
|
DUMP(&query_cache);
|
|
BLOCK_LOCK_WR(query_block);
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
|
|
Query_cache_query *header = query_block->query();
|
|
#ifndef DBUG_OFF
|
|
if (header->result() == 0)
|
|
{
|
|
DBUG_PRINT("error", ("end of data whith no result. query '%s'",
|
|
header->query()));
|
|
query_cache.wreck(__LINE__, "");
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
#endif
|
|
header->found_rows(current_thd->limit_found_rows);
|
|
header->result()->type = Query_cache_block::RESULT;
|
|
net->query_cache_query=0;
|
|
header->writer(0);
|
|
BLOCK_UNLOCK_WR(query_block);
|
|
}
|
|
else
|
|
{
|
|
// Cache was flushed or resized and query was deleted => do nothing
|
|
STRUCT_UNLOCK(&query_cache.structure_guard_mutex);
|
|
}
|
|
net->query_cache_query=0;
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
void query_cache_invalidate_by_MyISAM_filename(const char *filename)
|
|
{
|
|
query_cache.invalidate_by_MyISAM_filename(filename);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Query_cache methods
|
|
*****************************************************************************/
|
|
|
|
Query_cache::Query_cache(ulong query_cache_limit,
|
|
ulong min_allocation_unit,
|
|
ulong min_result_data_size,
|
|
uint def_query_hash_size ,
|
|
uint def_table_hash_size)
|
|
:query_cache_size(0),
|
|
query_cache_limit(query_cache_limit),
|
|
queries_in_cache(0), hits(0), inserts(0), refused(0),
|
|
min_allocation_unit(min_allocation_unit),
|
|
min_result_data_size(min_result_data_size),
|
|
def_query_hash_size(def_query_hash_size),
|
|
def_table_hash_size(def_table_hash_size),
|
|
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 = min_allocation_unit;
|
|
set_if_bigger(this->min_result_data_size,min_allocation_unit);
|
|
}
|
|
|
|
|
|
ulong Query_cache::resize(ulong query_cache_size)
|
|
{
|
|
/*
|
|
TODO:
|
|
When will be realized pack() optimize case when
|
|
query_cache_size < this->query_cache_size
|
|
|
|
Try to copy old cache in new memory
|
|
*/
|
|
DBUG_ENTER("Query_cache::resize");
|
|
DBUG_PRINT("qcache", ("from %lu to %lu",this->query_cache_size,
|
|
query_cache_size));
|
|
free_cache(0);
|
|
this->query_cache_size=query_cache_size;
|
|
DBUG_RETURN(init_cache());
|
|
}
|
|
|
|
|
|
void Query_cache::store_query(THD *thd, TABLE_LIST *tables_used)
|
|
{
|
|
/*
|
|
TODO:
|
|
Maybe better convert keywords to upper case when query stored/compared.
|
|
(Not important at this stage)
|
|
*/
|
|
TABLE_COUNTER_TYPE tables;
|
|
DBUG_ENTER("Query_cache::store_query");
|
|
if (query_cache_size == 0)
|
|
DBUG_VOID_RETURN;
|
|
|
|
if ((tables = is_cacheable(thd, thd->query_length,
|
|
thd->query, &thd->lex, tables_used)))
|
|
{
|
|
NET *net = &thd->net;
|
|
byte flags = (thd->client_capabilities & CLIENT_LONG_FLAG ? 0x80 : 0);
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
|
|
if (query_cache_size == 0)
|
|
DBUG_VOID_RETURN;
|
|
DUMP(this);
|
|
|
|
/*
|
|
Prepare flags:
|
|
most significant bit - CLIENT_LONG_FLAG,
|
|
other - charset number (0 no charset convertion)
|
|
*/
|
|
if (thd->convert_set != 0)
|
|
{
|
|
flags|= (byte) thd->convert_set->number();
|
|
DBUG_ASSERT(thd->convert_set->number() < 128);
|
|
}
|
|
|
|
/* Check if another thread is processing the same query? */
|
|
thd->query[thd->query_length] = (char) flags;
|
|
Query_cache_block *competitor = (Query_cache_block *)
|
|
hash_search(&queries, thd->query, thd->query_length+1);
|
|
DBUG_PRINT("qcache", ("competitor 0x%lx, flags %x", (ulong) competitor,
|
|
flags));
|
|
if (competitor == 0)
|
|
{
|
|
/* Query is not in cache and no one is working with it; Store it */
|
|
thd->query[thd->query_length] = (char) flags;
|
|
Query_cache_block *query_block;
|
|
query_block= write_block_data(thd->query_length+1,
|
|
(gptr) thd->query,
|
|
ALIGN_SIZE(sizeof(Query_cache_query)),
|
|
Query_cache_block::QUERY, tables, 1);
|
|
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 (hash_insert(&queries, (byte*) 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, tables))
|
|
{
|
|
refused++;
|
|
DBUG_PRINT("warning", ("tables list including failed"));
|
|
hash_delete(&queries, (char *) query_block);
|
|
header->unlock_n_destroy();
|
|
free_memory_block(query_block);
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
double_linked_list_simple_include(query_block, &queries_blocks);
|
|
inserts++;
|
|
queries_in_cache++;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
net->query_cache_query = (gptr) query_block;
|
|
header->writer(net);
|
|
// 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
|
|
statistic_increment(refused, &structure_guard_mutex);
|
|
|
|
end:
|
|
thd->query[thd->query_length]= 0; // Restore end null
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
my_bool
|
|
Query_cache::send_result_to_client(THD *thd, char *sql, uint query_length)
|
|
{
|
|
Query_cache_query *query;
|
|
Query_cache_block *first_result_block, *result_block;
|
|
Query_cache_block_table *block_table, *block_table_end;
|
|
byte flags;
|
|
DBUG_ENTER("Query_cache::send_result_to_client");
|
|
|
|
if (query_cache_size == 0 ||
|
|
/*
|
|
it is not possible to check has_transactions() function of handler
|
|
because tables not opened yet
|
|
*/
|
|
(thd->options & (OPTION_NOT_AUTO_COMMIT | OPTION_BEGIN)) ||
|
|
thd->query_cache_type == 0)
|
|
|
|
{
|
|
DBUG_PRINT("qcache", ("query cache disabled on not in autocommit mode"));
|
|
goto err;
|
|
}
|
|
/*
|
|
We can't cache the query if we are using a temporary table because
|
|
we don't know if the query is using a temporary table.
|
|
|
|
TODO: We could parse the query and then check if the query used
|
|
a temporary table.
|
|
*/
|
|
if (thd->temporary_tables != 0 || !thd->safe_to_cache_query)
|
|
{
|
|
DBUG_PRINT("qcache", ("SELECT is non-cacheable"));
|
|
goto err;
|
|
}
|
|
|
|
/* Test if the query is a SELECT */
|
|
while (*sql == ' ' || *sql == '\t')
|
|
{
|
|
sql++;
|
|
query_length--;
|
|
}
|
|
if (toupper(sql[0]) != 'S' || toupper(sql[1]) != 'E' ||
|
|
toupper(sql[2]) !='L')
|
|
{
|
|
DBUG_PRINT("qcache", ("The statement is not a SELECT; Not cached"));
|
|
goto err;
|
|
}
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size == 0)
|
|
{
|
|
DBUG_PRINT("qcache", ("query cache disabled and not in autocommit mode"));
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
goto err;
|
|
}
|
|
DBUG_PRINT("qcache", (" sql %u '%s'", query_length, sql));
|
|
Query_cache_block *query_block;
|
|
|
|
/*
|
|
prepare flags:
|
|
Most significant bit - CLIENT_LONG_FLAG,
|
|
Other - charset number (0 no charset convertion)
|
|
*/
|
|
flags = (thd->client_capabilities & CLIENT_LONG_FLAG ? 0x80 : 0);
|
|
if (thd->convert_set != 0)
|
|
{
|
|
flags |= (byte) thd->convert_set->number();
|
|
DBUG_ASSERT(thd->convert_set->number() < 128);
|
|
}
|
|
|
|
sql[query_length] = (char) flags;
|
|
query_block = (Query_cache_block *) hash_search(&queries, sql,
|
|
query_length+1);
|
|
sql[query_length] = '\0';
|
|
|
|
/* Quick abort on unlocked data */
|
|
if (query_block == 0 ||
|
|
query_block->query()->result() == 0 ||
|
|
query_block->query()->result()->type != Query_cache_block::RESULT)
|
|
{
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_PRINT("qcache", ("No query in query hash or no results"));
|
|
goto err;
|
|
}
|
|
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;
|
|
}
|
|
DBUG_PRINT("qcache", ("Query have result 0x%lx", (ulong) query));
|
|
|
|
// 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;
|
|
bzero((char*) &table_list,sizeof(table_list));
|
|
|
|
Query_cache_table *table = block_table->parent;
|
|
table_list.db = table->db();
|
|
table_list.name = table_list.real_name = table->table();
|
|
if (check_table_access(thd,SELECT_ACL,&table_list))
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("probably no SELECT access to %s.%s => return to normal processing",
|
|
table_list.db, table_list.name));
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
goto err;
|
|
}
|
|
}
|
|
move_to_query_list_end(query_block);
|
|
hits++;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
|
|
/*
|
|
Send cached result to client
|
|
*/
|
|
do
|
|
{
|
|
DBUG_PRINT("qcache", ("Results (len %lu, used %lu, headers %lu)",
|
|
result_block->length, result_block->used,
|
|
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;
|
|
} while (result_block != first_result_block);
|
|
|
|
thd->limit_found_rows = query->found_rows();
|
|
|
|
BLOCK_UNLOCK_RD(query_block);
|
|
DBUG_RETURN(0);
|
|
|
|
err:
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
/*
|
|
Remove all cached queries that uses any of the tables in the list
|
|
*/
|
|
|
|
void Query_cache::invalidate(TABLE_LIST *tables_used)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (table list)");
|
|
if (query_cache_size > 0)
|
|
{
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size > 0)
|
|
{
|
|
DUMP(this);
|
|
for ( ; tables_used; tables_used=tables_used->next)
|
|
invalidate_table(tables_used);
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*
|
|
Remove all cached queries that uses the given table
|
|
*/
|
|
|
|
void Query_cache::invalidate(TABLE *table)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (table)");
|
|
if (query_cache_size > 0)
|
|
{
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size > 0)
|
|
invalidate_table(table);
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*
|
|
Remove all cached queries that uses the given table type.
|
|
TODO: This is currently used to invalidate InnoDB tables on commit.
|
|
We should remove this function and only invalidate tables
|
|
used in the transaction.
|
|
*/
|
|
|
|
void Query_cache::invalidate(Query_cache_table::query_cache_table_type type)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (type)");
|
|
if (query_cache_size > 0)
|
|
{
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
DUMP(this);
|
|
if (query_cache_size > 0 && tables_blocks[type] != 0)
|
|
{
|
|
Query_cache_block *table_block = tables_blocks[type];
|
|
do
|
|
{
|
|
/* Store next block address defore deleting the current block */
|
|
Query_cache_block *next = table_block->next;
|
|
invalidate_table(table_block);
|
|
#ifdef TO_BE_DELETED
|
|
if (next == table_block) // End of list
|
|
break;
|
|
#endif
|
|
table_block = next;
|
|
} while (table_block != tables_blocks[type]);
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
/*
|
|
Remove all cached queries that uses the given database
|
|
*/
|
|
|
|
void Query_cache::invalidate(char *db)
|
|
{
|
|
DBUG_ENTER("Query_cache::invalidate (db)");
|
|
if (query_cache_size > 0)
|
|
{
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size > 0)
|
|
{
|
|
DUMP(this);
|
|
int i = 0;
|
|
for(; i < (int) Query_cache_table::TYPES_NUMBER; i++)
|
|
{
|
|
if (tables_blocks[i] != 0) // Cache not empty
|
|
{
|
|
Query_cache_block *table_block = tables_blocks[i];
|
|
do
|
|
{
|
|
/*
|
|
Store next block address defore deletetion of current block
|
|
*/
|
|
Query_cache_block *next = table_block->next;
|
|
|
|
invalidate_table_in_db(table_block, db);
|
|
#ifdef TO_BE_DELETED
|
|
if (table_block == next)
|
|
break;
|
|
#endif
|
|
table_block = next;
|
|
} while (table_block != tables_blocks[i]);
|
|
}
|
|
}
|
|
}
|
|
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");
|
|
if (query_cache_size > 0)
|
|
{
|
|
/* Calculate the key outside the lock to make the lock shorter */
|
|
char key[MAX_DBKEY_LENGTH];
|
|
uint key_length= filename_2_table_key(key, filename);
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size > 0) // Safety if cache removed
|
|
{
|
|
Query_cache_block *table_block;
|
|
if ((table_block = (Query_cache_block*) hash_search(&tables, key,
|
|
key_length)))
|
|
invalidate_table(table_block);
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
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);
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* Join result in cache in 1 block (if result length > join_limit) */
|
|
|
|
void Query_cache::pack(ulong join_limit, uint iteration_limit)
|
|
{
|
|
DBUG_ENTER("Query_cache::pack");
|
|
uint i = 0;
|
|
do
|
|
{
|
|
pack_cache();
|
|
} while ((++i < iteration_limit) && join_results(join_limit));
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::destroy()
|
|
{
|
|
DBUG_ENTER("Query_cache::destroy");
|
|
free_cache(1);
|
|
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);
|
|
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;
|
|
|
|
DBUG_ENTER("Query_cache::init_cache");
|
|
if (!initialized)
|
|
init();
|
|
approx_additional_data_size = (sizeof(Query_cache) +
|
|
sizeof(gptr)*(def_query_hash_size+
|
|
def_query_hash_size));
|
|
if (query_cache_size < approx_additional_data_size)
|
|
goto err;
|
|
|
|
query_cache_size -= approx_additional_data_size;
|
|
|
|
/*
|
|
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;
|
|
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;
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (query_cache_size <= min_allocation_unit)
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
(" query_cache_size <= min_allocation_unit => cache disabled"));
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
goto err;
|
|
}
|
|
|
|
if (!(cache = (byte *)
|
|
my_malloc_lock(query_cache_size+additional_data_size, MYF(0))))
|
|
{
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
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);
|
|
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
|
|
free_memory= 0;
|
|
insert_into_free_memory_list(first_block);
|
|
|
|
DUMP(this);
|
|
|
|
VOID(hash_init(&queries,def_query_hash_size, 0, 0,
|
|
query_cache_query_get_key, 0, 0));
|
|
VOID(hash_init(&tables,def_table_hash_size, 0, 0,
|
|
query_cache_table_get_key, 0, 0));
|
|
|
|
queries_in_cache = 0;
|
|
queries_blocks = 0;
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
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;
|
|
free_memory= 0;
|
|
bins= 0;
|
|
steps= 0;
|
|
cache= 0;
|
|
mem_bin_num= mem_bin_steps= 0;
|
|
queries_in_cache= 0;
|
|
first_block= 0;
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::free_cache(my_bool destruction)
|
|
{
|
|
DBUG_ENTER("Query_cache::free_cache");
|
|
if (query_cache_size > 0)
|
|
{
|
|
if (!destruction)
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
|
|
flush_cache();
|
|
#ifndef DBUG_OFF
|
|
if (bins[0].free_blocks == 0)
|
|
{
|
|
wreck(__LINE__,"no free memory found in (bins[0].free_blocks");
|
|
}
|
|
#endif
|
|
|
|
/* Becasue we did a flush, all cache memory must be in one this block */
|
|
bins[0].free_blocks->destroy();
|
|
DBUG_PRINT("qcache", ("free memory %lu (should be %lu)",
|
|
free_memory , query_cache_size));
|
|
my_free((gptr) cache, MYF(MY_ALLOW_ZERO_PTR));
|
|
make_disabled();
|
|
hash_free(&queries);
|
|
hash_free(&tables);
|
|
if (!destruction)
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
}
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Free block data
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
The following assumes we have a lock on the cache
|
|
*/
|
|
|
|
void Query_cache::flush_cache()
|
|
{
|
|
while (queries_blocks != 0)
|
|
{
|
|
BLOCK_LOCK_WR(queries_blocks);
|
|
free_query(queries_blocks);
|
|
}
|
|
}
|
|
|
|
/*
|
|
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);
|
|
DBUG_RETURN(0);
|
|
}
|
|
}
|
|
DBUG_RETURN(1); // Nothing to remove
|
|
}
|
|
|
|
/*
|
|
Free query from query cache.
|
|
query_block must be locked for writing.
|
|
This function will remove (and destroy) the lock for the query.
|
|
*/
|
|
|
|
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() ));
|
|
|
|
queries_in_cache--;
|
|
hash_delete(&queries,(byte *) query_block);
|
|
|
|
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)
|
|
{
|
|
Query_cache_block *block = result_block;
|
|
do
|
|
{
|
|
Query_cache_block *current = block;
|
|
block = block->next;
|
|
free_memory_block(current);
|
|
} while (block != result_block);
|
|
}
|
|
|
|
query->unlock_n_destroy();
|
|
free_memory_block(query_block);
|
|
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Query data creation
|
|
*****************************************************************************/
|
|
|
|
Query_cache_block *
|
|
Query_cache::write_block_data(ulong data_len, gptr data,
|
|
ulong header_len,
|
|
Query_cache_block::block_type type,
|
|
TABLE_COUNTER_TYPE ntab,
|
|
my_bool under_guard)
|
|
{
|
|
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;
|
|
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(len, min_allocation_unit),
|
|
1, 0, under_guard);
|
|
if (block != 0)
|
|
{
|
|
block->type = type;
|
|
block->n_tables = ntab;
|
|
block->used = len;
|
|
|
|
memcpy((void*) (((byte *) block)+ all_headers_len),
|
|
(void*) 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, gptr 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, 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));
|
|
*current_block=0; // Mark error
|
|
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...
|
|
if (last_block_free_space < data_len &&
|
|
append_next_free_block(last_block,
|
|
max(data_len - last_block_free_space,
|
|
QUERY_CACHE_MIN_RESULT_DATA_SIZE)))
|
|
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)
|
|
{
|
|
// TODO: Try get memory from next free block (if exist) (is it needed?)
|
|
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,
|
|
(gptr)(((byte*)data)+last_block_free_space),
|
|
query_block,
|
|
Query_cache_block::RES_CONT);
|
|
/*
|
|
new_block may be not 0 even !success (if write_result_data
|
|
allocate small block but failed 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((void*) (((byte*) last_block) + last_block->used), (void*) 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, gptr 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);
|
|
if (success)
|
|
{
|
|
// It is success (nobody can prevent us write data)
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
byte *rest = (byte*) data;
|
|
Query_cache_block *block = *result_block;
|
|
uint headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
// 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((void*)(((byte*) block)+headers_len), (void*) rest, length);
|
|
rest += length;
|
|
block = block->next;
|
|
type = Query_cache_block::RES_CONT;
|
|
} while (block != *result_block);
|
|
}
|
|
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);
|
|
}
|
|
|
|
/*
|
|
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)
|
|
{
|
|
ulong all_headers_len = (ALIGN_SIZE(sizeof(Query_cache_block)) +
|
|
ALIGN_SIZE(sizeof(Query_cache_result)));
|
|
ulong len = data_len + all_headers_len;
|
|
DBUG_ENTER("Query_cache::allocate_data_chain");
|
|
DBUG_PRINT("qcache", ("data_len %lu, all_headers_len %lu",
|
|
data_len, all_headers_len));
|
|
|
|
*result_block = allocate_block(max(min_result_data_size,len),
|
|
min_result_data_size == 0,
|
|
all_headers_len + min_result_data_size,
|
|
1);
|
|
my_bool success = (*result_block != 0);
|
|
if (success)
|
|
{
|
|
Query_cache_block *new_block= *result_block;
|
|
new_block->n_tables = 0;
|
|
new_block->used = 0;
|
|
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);
|
|
|
|
if (new_block->length < len)
|
|
{
|
|
/*
|
|
We got less memory then we need (no big memory blocks) =>
|
|
Continue to allocated more blocks until we got everything we need.
|
|
*/
|
|
Query_cache_block *next_block;
|
|
if ((success = allocate_data_chain(&next_block,
|
|
len - new_block->length,
|
|
query_block)))
|
|
double_linked_list_join(new_block, next_block);
|
|
}
|
|
if (success)
|
|
{
|
|
new_block->used = min(len, new_block->length);
|
|
|
|
DBUG_PRINT("qcache", ("Block len %lu used %lu",
|
|
new_block->length, new_block->used));
|
|
}
|
|
else
|
|
DBUG_PRINT("warning", ("Can't allocate block for continue"));
|
|
}
|
|
else
|
|
DBUG_PRINT("warning", ("Can't allocate block for results"));
|
|
DBUG_RETURN(success);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Tables management
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
Invalidate the first table in the table_list
|
|
*/
|
|
|
|
void Query_cache::invalidate_table(TABLE_LIST *table_list)
|
|
{
|
|
if (table_list->table != 0)
|
|
invalidate_table(table_list->table); // Table is open
|
|
else
|
|
{
|
|
char key[MAX_DBKEY_LENGTH];
|
|
uint key_length;
|
|
Query_cache_block *table_block;
|
|
key_length=(uint) (strmov(strmov(key,table_list->db)+1,
|
|
table_list->real_name) -key)+ 1;
|
|
|
|
// We don't store temporary tables => no key_length+=4 ...
|
|
if ((table_block = (Query_cache_block*)
|
|
hash_search(&tables,key,key_length)))
|
|
invalidate_table(table_block);
|
|
}
|
|
}
|
|
|
|
void Query_cache::invalidate_table(TABLE *table)
|
|
{
|
|
Query_cache_block *table_block;
|
|
if ((table_block = ((Query_cache_block*)
|
|
hash_search(&tables, table->table_cache_key,
|
|
table->key_length))))
|
|
invalidate_table(table_block);
|
|
}
|
|
|
|
void Query_cache::invalidate_table_in_db(Query_cache_block *table_block,
|
|
char *db)
|
|
{
|
|
/*
|
|
table key consist of data_base_name + '\0' + table_name +'\0'...
|
|
=> we may use strcmp to compare database names.
|
|
*/
|
|
if (strcmp(db, (char*)(table_block->table()->db())) == 0)
|
|
invalidate_table(table_block);
|
|
}
|
|
|
|
|
|
void Query_cache::invalidate_table(Query_cache_block *table_block)
|
|
{
|
|
Query_cache_block_table *list_root = table_block->table(0);
|
|
while (list_root->next != list_root)
|
|
{
|
|
Query_cache_block *query_block = list_root->next->block();
|
|
BLOCK_LOCK_WR(query_block);
|
|
free_query(query_block);
|
|
}
|
|
}
|
|
|
|
|
|
my_bool Query_cache::register_all_tables(Query_cache_block *block,
|
|
TABLE_LIST *tables_used,
|
|
TABLE_COUNTER_TYPE tables)
|
|
{
|
|
TABLE_COUNTER_TYPE n;
|
|
DBUG_PRINT("qcache", ("register tables block 0x%lx, n %d, header %x",
|
|
(ulong) block, (int) tables,
|
|
(int) ALIGN_SIZE(sizeof(Query_cache_block))));
|
|
|
|
Query_cache_block_table *block_table = block->table(0);
|
|
|
|
for (n=0; tables_used; tables_used=tables_used->next, n++, block_table++)
|
|
{
|
|
DBUG_PRINT("qcache",
|
|
("table %s, db %s, openinfo at 0x%lx, keylen %u, key at 0x%lx",
|
|
tables_used->real_name, tables_used->db,
|
|
(ulong) tables_used->table,
|
|
tables_used->table->key_length,
|
|
(ulong) tables_used->table->table_cache_key));
|
|
block_table->n=n;
|
|
if (!insert_table(tables_used->table->key_length,
|
|
tables_used->table->table_cache_key, block_table,
|
|
Query_cache_table::type_convertion(tables_used->table->
|
|
db_type)))
|
|
break;
|
|
/*
|
|
TODO: (Low priority)
|
|
The following has to be recoded to not test for a specific table
|
|
type but instead call a handler function that does this for us.
|
|
Something like the following:
|
|
|
|
tables_used->table->file->register_used_filenames(callback,
|
|
first_argument);
|
|
*/
|
|
if (tables_used->table->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];
|
|
uint key_length =filename_2_table_key(key, table->table->filename);
|
|
(++block_table)->n= ++n;
|
|
if (!insert_table(key_length, key, block_table,
|
|
Query_cache_table::type_convertion(DB_TYPE_MYISAM)))
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
|
|
err:
|
|
if (tables_used)
|
|
{
|
|
DBUG_PRINT("qcache", ("failed at table %d", (int) n));
|
|
/* Unlink the tables we allocated above */
|
|
for (Query_cache_block_table *tmp = block->table(0) ;
|
|
tmp != block_table;
|
|
tmp++)
|
|
unlink_table(tmp);
|
|
}
|
|
return (tables_used == 0);
|
|
}
|
|
|
|
/*
|
|
Insert used tablename in cache
|
|
Returns 0 on error
|
|
*/
|
|
|
|
my_bool
|
|
Query_cache::insert_table(uint key_len, char *key,
|
|
Query_cache_block_table *node,
|
|
Query_cache_table::query_cache_table_type type)
|
|
{
|
|
DBUG_ENTER("Query_cache::insert_table");
|
|
DBUG_PRINT("qcache", ("insert table node 0x%lx, len %d",
|
|
(ulong)node, key_len));
|
|
|
|
Query_cache_block *table_block = ((Query_cache_block *)
|
|
hash_search(&tables, key, key_len));
|
|
|
|
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, (gptr) key,
|
|
ALIGN_SIZE(sizeof(Query_cache_table)),
|
|
Query_cache_block::TABLE,
|
|
1, 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();
|
|
header->type(type);
|
|
double_linked_list_simple_include(table_block,
|
|
&tables_blocks[type]);
|
|
Query_cache_block_table *list_root = table_block->table(0);
|
|
list_root->n = 0;
|
|
list_root->next = list_root->prev = list_root;
|
|
if (hash_insert(&tables, (const byte *) 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();
|
|
/*
|
|
TODO: Eliminate strlen() by sending this to the function
|
|
To do this we have to add db_len to the TABLE_LIST and TABLE structures.
|
|
*/
|
|
header->table(db + strlen(db) + 1);
|
|
}
|
|
|
|
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();
|
|
DBUG_RETURN(1);
|
|
}
|
|
|
|
|
|
void Query_cache::unlink_table(Query_cache_block_table *node)
|
|
{
|
|
node->prev->next = node->next;
|
|
node->next->prev = node->prev;
|
|
Query_cache_block_table *neighbour = node->next;
|
|
if (neighbour->next == neighbour)
|
|
{
|
|
// list is empty (neighbor is root of list)
|
|
Query_cache_block *table_block = neighbour->block();
|
|
double_linked_list_exclude(table_block,
|
|
&tables_blocks[table_block->table()->type()]);
|
|
hash_delete(&tables,(byte *) table_block);
|
|
free_memory_block(table_block);
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************
|
|
Free memory management
|
|
*****************************************************************************/
|
|
|
|
Query_cache_block *
|
|
Query_cache::allocate_block(ulong len, my_bool not_less, ulong min,
|
|
my_bool under_guard)
|
|
{
|
|
DBUG_ENTER("Query_cache::allocate_n_lock_block");
|
|
DBUG_PRINT("qcache", ("len %lu, not less %d, min %lu, uder_guard %d",
|
|
len, not_less,min,under_guard));
|
|
|
|
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
|
|
}
|
|
|
|
if (!under_guard)
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
|
|
/* 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));
|
|
}
|
|
|
|
if (!under_guard)
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
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;
|
|
first = list;
|
|
while (first->next != list && first->length < len)
|
|
first=first->next;
|
|
if (first->length >= len)
|
|
block=first;
|
|
}
|
|
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_n_unlock_memory_block");
|
|
block->used=0;
|
|
DBUG_PRINT("qcache",("first_block 0x%lx, block 0x%lx, pnext 0x%lx pprev 0x%lx",
|
|
(ulong) first_block, (ulong) block,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*)(((byte*) block)+len);
|
|
|
|
new_block->init(block->length - len);
|
|
block->length=len;
|
|
new_block->pnext = block->pnext;
|
|
block->pnext = new_block;
|
|
new_block->pprev = block;
|
|
new_block->pnext->pprev = new_block;
|
|
|
|
insert_into_free_memory_list(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,
|
|
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, (ulong) first_block->pnext,
|
|
(ulong) block_in_list));
|
|
|
|
exclude_from_free_memory_list(block_in_list);
|
|
second_block = first_block->pnext;
|
|
// May be was not free block
|
|
second_block->used=0;
|
|
second_block->destroy();
|
|
|
|
first_block->length += second_block->length;
|
|
first_block->pnext = second_block->pnext;
|
|
second_block->pnext->pprev = first_block;
|
|
|
|
DBUG_RETURN(first_block);
|
|
}
|
|
|
|
|
|
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->is_free())
|
|
{
|
|
ulong old_len = block->length;
|
|
exclude_from_free_memory_list(next_block);
|
|
next_block->destroy();
|
|
|
|
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;
|
|
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)
|
|
{
|
|
int i;
|
|
DBUG_ENTER("Query_cache::find_bin");
|
|
// Begin small blocks to big (small blocks frequently asked)
|
|
for (i=mem_bin_steps - 1; i > 0 && steps[i-1].size < size; i--) ;
|
|
if (i == 0)
|
|
{
|
|
// first bin not subordinate of common rules
|
|
DBUG_PRINT("qcache", ("first bin (# 0), size %lu",size));
|
|
DBUG_RETURN(0);
|
|
}
|
|
uint bin = steps[i].idx - (uint)((size - steps[i].size)/steps[i].increment);
|
|
DBUG_PRINT("qcache", ("bin %u step %u, size %lu", bin, i, 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;
|
|
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
|
|
{
|
|
point->next = (*list_pointer);
|
|
point->prev = (*list_pointer)->prev;
|
|
point->prev->next = point;
|
|
(*list_pointer)->prev = point;
|
|
(*list_pointer) = 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 (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
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
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)
|
|
{
|
|
TABLE_COUNTER_TYPE tables = 0;
|
|
DBUG_ENTER("Query_cache::is_cacheable");
|
|
|
|
if (lex->sql_command == SQLCOM_SELECT &&
|
|
thd->temporary_tables == 0 &&
|
|
(thd->query_cache_type == 1 ||
|
|
(thd->query_cache_type == 2 && (lex->select->options &
|
|
OPTION_TO_QUERY_CACHE))) &&
|
|
thd->safe_to_cache_query)
|
|
{
|
|
my_bool has_transactions = 0;
|
|
DBUG_PRINT("qcache", ("options %lx %lx, type %u",
|
|
OPTION_TO_QUERY_CACHE,
|
|
lex->select->options,
|
|
(int) thd->query_cache_type));
|
|
|
|
for (; tables_used; tables_used=tables_used->next)
|
|
{
|
|
tables++;
|
|
DBUG_PRINT("qcache", ("table %s, db %s, type %u",
|
|
tables_used->real_name,
|
|
tables_used->db, tables_used->table->db_type));
|
|
has_transactions = (has_transactions ||
|
|
tables_used->table->file->has_transactions());
|
|
|
|
if (tables_used->table->db_type == DB_TYPE_MRG_ISAM)
|
|
{
|
|
DBUG_PRINT("qcache", ("select not cacheable: used MRG_ISAM table(s)"));
|
|
DBUG_RETURN(0);
|
|
}
|
|
if (tables_used->table->db_type == DB_TYPE_MRG_MYISAM)
|
|
{
|
|
ha_myisammrg *handler = (ha_myisammrg *)tables_used->table->file;
|
|
MYRG_INFO *file = handler->myrg_info();
|
|
tables+= (file->end_table - file->open_tables);
|
|
}
|
|
}
|
|
|
|
if ((thd->options & (OPTION_NOT_AUTO_COMMIT | OPTION_BEGIN)) &&
|
|
has_transactions)
|
|
{
|
|
DBUG_PRINT("qcache", ("not in autocommin mode"));
|
|
DBUG_RETURN(0);
|
|
}
|
|
DBUG_PRINT("qcache", ("select is using %d tables", tables));
|
|
DBUG_RETURN(tables);
|
|
}
|
|
|
|
DBUG_PRINT("qcache",
|
|
("not interesting query: %d or not cacheable, options %lx %lx, type %u",
|
|
(int) lex->sql_command,
|
|
OPTION_TO_QUERY_CACHE,
|
|
lex->select->options,
|
|
(int) thd->query_cache_type));
|
|
DBUG_RETURN(0);
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
Packing
|
|
*****************************************************************************/
|
|
|
|
void Query_cache::pack_cache()
|
|
{
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
byte *border = 0;
|
|
Query_cache_block *before = 0;
|
|
ulong gap = 0;
|
|
my_bool ok = 1;
|
|
Query_cache_block *block = first_block;
|
|
DBUG_ENTER("Query_cache::pack_cache");
|
|
DUMP(this);
|
|
|
|
if (first_block)
|
|
{
|
|
do
|
|
{
|
|
ok = move_by_type(&border, &before, &gap, block);
|
|
block = block->pnext;
|
|
} while (ok && block != first_block);
|
|
|
|
if (border != 0)
|
|
{
|
|
Query_cache_block *new_block = (Query_cache_block *) border;
|
|
new_block->init(gap);
|
|
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);
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
my_bool Query_cache::move_by_type(byte **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 = (byte *) 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();
|
|
DBUG_PRINT("qcache", ("added to gap (%lu)", *gap));
|
|
break;
|
|
}
|
|
case Query_cache_block::TABLE:
|
|
{
|
|
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;
|
|
char *data = (char*) block->data();
|
|
byte *key;
|
|
uint key_length;
|
|
key=query_cache_table_get_key((byte*) block, &key_length,0);
|
|
hash_search(&tables, key, key_length);
|
|
|
|
block->destroy();
|
|
new_block->init(len);
|
|
new_block->type=Query_cache_block::TABLE;
|
|
new_block->used=used;
|
|
new_block->n_tables=1;
|
|
memcpy((char*) new_block->data(), data, len-new_block->headers_len());
|
|
relink(block, new_block, next, prev, pnext, pprev);
|
|
if (tables_blocks[new_block->table()->type()] == block)
|
|
tables_blocks[new_block->table()->type()] = new_block;
|
|
|
|
Query_cache_block_table *nlist_root = new_block->table(0);
|
|
nlist_root->n = 0;
|
|
nlist_root->next = (tnext == list_root ? nlist_root : tnext);
|
|
nlist_root->prev = (tprev == list_root ? nlist_root: tnext);
|
|
tnext->prev = list_root;
|
|
tprev->next = list_root;
|
|
*border += len;
|
|
*before = new_block;
|
|
/* Fix hash to point at moved block */
|
|
hash_replace(&tables, tables.current_record, (byte*) 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:
|
|
{
|
|
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();
|
|
byte *key;
|
|
uint key_length;
|
|
key=query_cache_query_get_key((byte*) block, &key_length,0);
|
|
hash_search(&queries, key, key_length);
|
|
|
|
memcpy((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;
|
|
memcpy((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;
|
|
for (TABLE_COUNTER_TYPE j=0; j < n_tables; j++)
|
|
{
|
|
Query_cache_block_table *block_table = new_block->table(j);
|
|
block_table->next->prev = block_table;
|
|
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 );
|
|
}
|
|
NET *net = new_block->query()->writer();
|
|
if (net != 0)
|
|
{
|
|
net->query_cache_query = (gptr) new_block;
|
|
}
|
|
/* Fix hash to point at moved block */
|
|
hash_replace(&queries, queries.current_record, (byte*) 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;
|
|
memcpy((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;
|
|
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;
|
|
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)
|
|
{
|
|
nblock->prev = (prev == oblock ? nblock : prev); //check pointer to himself
|
|
nblock->next = (next == oblock ? nblock : next);
|
|
prev->next=nblock;
|
|
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");
|
|
|
|
STRUCT_LOCK(&structure_guard_mutex);
|
|
if (queries_blocks != 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(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);
|
|
byte *write_to = (byte*) 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 );
|
|
}
|
|
STRUCT_UNLOCK(&structure_guard_mutex);
|
|
DBUG_RETURN(has_moving);
|
|
}
|
|
|
|
|
|
uint Query_cache::filename_2_table_key (char *key, const char *path)
|
|
{
|
|
char tablename[FN_REFLEN+2], *filename, *dbname;
|
|
Query_cache_block *table_block;
|
|
uint db_length;
|
|
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
|
|
****************************************************************************/
|
|
|
|
#ifndef DBUG_OFF
|
|
|
|
void Query_cache::wreck(uint line, const char *message)
|
|
{
|
|
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", ("=================================="));
|
|
current_thd->killed = 1;
|
|
bins_dump();
|
|
cache_dump();
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
|
|
void Query_cache::bins_dump()
|
|
{
|
|
uint i;
|
|
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()
|
|
{
|
|
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()
|
|
{
|
|
DBUG_PRINT("qcache", ("------------------"));
|
|
DBUG_PRINT("qcache", (" QUERIES"));
|
|
DBUG_PRINT("qcache", ("------------------"));
|
|
if (queries_blocks != 0)
|
|
{
|
|
Query_cache_block *block = queries_blocks;
|
|
do
|
|
{
|
|
uint len;
|
|
char *str = (char*) query_cache_query_get_key((byte*) block, &len, 0);
|
|
byte flags = (byte) str[len-1];
|
|
DBUG_PRINT("qcache", ("%u (%u,%u) %.*s",len,
|
|
((flags & QUERY_CACHE_CLIENT_LONG_FLAG_MASK)? 1:0),
|
|
(flags & QUERY_CACHE_CHARSET_CONVERT_MASK), len,
|
|
str));
|
|
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));
|
|
|
|
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()
|
|
{
|
|
DBUG_PRINT("qcache", ("--------------------"));
|
|
DBUG_PRINT("qcache", ("TABLES"));
|
|
DBUG_PRINT("qcache", ("--------------------"));
|
|
for (int i=0; i < (int) Query_cache_table::TYPES_NUMBER; i++)
|
|
{
|
|
DBUG_PRINT("qcache", ("--- type %u", i));
|
|
if (tables_blocks[i] != 0)
|
|
{
|
|
Query_cache_block *table_block = tables_blocks[i];
|
|
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[i]);
|
|
}
|
|
else
|
|
DBUG_PRINT("qcache", ("no tables in list"));
|
|
}
|
|
DBUG_PRINT("qcache", ("--------------------"));
|
|
}
|
|
#endif /* DBUG_OFF */
|