/
sql_cache.cc
5252 lines (4641 loc) · 160 KB
/
sql_cache.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Copyright (c) 2000, 2013, Oracle and/or its affiliates.
Copyright (c) 2010, 2013, Monty Program Ab
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA */
/*
Description of the query cache:
1. Query_cache object consists of
- query cache memory pool (cache)
- queries hash (queries)
- tables hash (tables)
- list of blocks ordered as they allocated in memory
(first_block)
- list of queries block (queries_blocks)
- list of used tables (tables_blocks)
2. Query cache memory pool (cache) consists of
- table of steps of memory bins allocation
- table of free memory bins
- blocks of memory
3. Memory blocks
Every memory block has the following structure:
+----------------------------------------------------------+
| Block header (Query_cache_block structure) |
+----------------------------------------------------------+
|Table of database table lists (used for queries & tables) |
+----------------------------------------------------------+
| Type depended header |
|(Query_cache_query, Query_cache_table, Query_cache_result)|
+----------------------------------------------------------+
| Data ... |
+----------------------------------------------------------+
Block header consists of:
- type:
FREE Free memory block
QUERY Query block
RESULT Ready to send result
RES_CONT Result's continuation
RES_BEG First block of results, that is not yet complete,
written to cache
RES_INCOMPLETE Allocated for results data block
TABLE Block with database table description
INCOMPLETE The destroyed block
- length of block (length)
- length of data & headers (used)
- physical list links (pnext/pprev) - used for the list of
blocks ordered as they are allocated in physical memory
- logical list links (next/prev) - used for queries block list, tables block
list, free memory block lists and list of results block in query
- number of elements in table of database table list (n_tables)
4. Query & results blocks
Query stored in cache consists of following blocks:
more more
recent+-------------+ old
<-----|Query block 1|------> double linked list of queries block
prev | | next
+-------------+
<-| table 0 |-> (see "Table of database table lists" description)
<-| table 1 |->
| ... | +--------------------------+
+-------------+ +-------------------------+ |
NET | | | V V |
struct| | +-+------------+ +------------+ |
<-----|query header |----->|Result block|-->|Result block|-+ doublelinked
writer| |result| |<--| | list of results
+-------------+ +------------+ +------------+
|charset | +------------+ +------------+ no table of dbtables
|encoding + | | result | | result |
|query text |<-----| header | | header |------+
+-------------+parent| | | |parent|
^ +------------+ +------------+ |
| |result data | |result data | |
| +------------+ +------------+ |
+---------------------------------------------------+
First query is registered. During the registration query block is
allocated. This query block is included in query hash and is linked
with appropriate database tables lists (if there is no appropriate
list exists it will be created).
Later when query has performed results is written into the result blocks.
A result block cannot be smaller then QUERY_CACHE_MIN_RESULT_DATA_SIZE.
When new result is written to cache it is appended to the last result
block, if no more free space left in the last block, new block is
allocated.
5. Table of database table lists.
For quick invalidation of queries all query are linked in lists on used
database tables basis (when table will be changed (insert/delete/...)
this queries will be removed from cache).
Root of such list is table block:
+------------+ list of used tables (used while invalidation of
<----| Table |-----> whole database)
prev| block |next +-----------+
| | +-----------+ |Query block|
| | |Query block| +-----------+
+------------+ +-----------+ | ... |
+->| table 0 |------>|table 0 |----->| table N |---+
|+-| |<------| |<-----| |<-+|
|| +------------+ | ... | | ... | ||
|| |table header| +-----------+ +-----------+ ||
|| +------------+ | ... | | ... | ||
|| |db name + | +-----------+ +-----------+ ||
|| |table name | ||
|| +------------+ ||
|+--------------------------------------------------------+|
+----------------------------------------------------------+
Table block is included into the tables hash (tables).
6. Free blocks, free blocks bins & steps of freeblock bins.
When we just started only one free memory block existed. All query
cache memory (that will be used for block allocation) were
containing in this block.
When a new block is allocated we find most suitable memory block
(minimal of >= required size). If such a block can not be found, we try
to find max block < required size (if we allocate block for results).
If there is no free memory, oldest query is removed from cache, and then
we try to allocate memory. Last step should be repeated until we find
suitable block or until there is no unlocked query found.
If the block is found and its length more then we need, it should be
split into 2 blocks.
New blocks cannot be smaller then min_allocation_unit_bytes.
When a block becomes free, its neighbor-blocks should be tested and if
there are free blocks among them, they should be joined into one block.
Free memory blocks are stored in bins according to their sizes.
The bins are stored in size-descending order.
These bins are distributed (by size) approximately logarithmically.
First bin (number 0) stores free blocks with
size <= query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2.
It is first (number 0) step.
On the next step distributed (1 + QUERY_CACHE_MEM_BIN_PARTS_INC) *
QUERY_CACHE_MEM_BIN_PARTS_MUL bins. This bins allocated in interval from
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 to
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 >>
QUERY_CACHE_MEM_BIN_STEP_PWR2
...
On each step interval decreases in 2 power of
QUERY_CACHE_MEM_BIN_STEP_PWR2
times, number of bins (that distributed on this step) increases. If on
the previous step there were N bins distributed , on the current there
would be distributed
(N + QUERY_CACHE_MEM_BIN_PARTS_INC) * QUERY_CACHE_MEM_BIN_PARTS_MUL
bins.
Last distributed bin stores blocks with size near min_allocation_unit
bytes.
For example:
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 = 100,
min_allocation_unit = 17,
QUERY_CACHE_MEM_BIN_STEP_PWR2 = 1,
QUERY_CACHE_MEM_BIN_PARTS_INC = 1,
QUERY_CACHE_MEM_BIN_PARTS_MUL = 1
(in followed picture showed right (low) bound of bin):
| 100>>1 50>>1 |25>>1|
| | | | | |
| 100 75 50 41 33 25 21 18 15| 12 | - bins right (low) bounds
|\---/\-----/\--------/\--------|---/ |
| 0 1 2 3 | | - steps
\-----------------------------/ \---/
bins that we store in cache this bin showed for example only
Calculation of steps/bins distribution is performed only when query cache
is resized.
When we need to find appropriate bin, first we should find appropriate
step, then we should calculate number of bins that are using data
stored in Query_cache_memory_bin_step structure.
Free memory blocks are sorted in bins in lists with size-ascending order
(more small blocks needed frequently then bigger one).
7. Packing cache.
Query cache packing is divided into two operation:
- pack_cache
- join_results
pack_cache moved all blocks to "top" of cache and create one block of free
space at the "bottom":
before pack_cache after pack_cache
+-------------+ +-------------+
| query 1 | | query 1 |
+-------------+ +-------------+
| table 1 | | table 1 |
+-------------+ +-------------+
| results 1.1 | | results 1.1 |
+-------------+ +-------------+
| free | | query 2 |
+-------------+ +-------------+
| query 2 | | table 2 |
+-------------+ ---> +-------------+
| table 2 | | results 1.2 |
+-------------+ +-------------+
| results 1.2 | | results 2 |
+-------------+ +-------------+
| free | | free |
+-------------+ | |
| results 2 | | |
+-------------+ | |
| free | | |
+-------------+ +-------------+
pack_cache scan blocks in physical address order and move every non-free
block "higher".
pack_cach remove every free block it finds. The length of the deleted block
is accumulated to the "gap". All non free blocks should be shifted with the
"gap" step.
join_results scans all complete queries. If the results of query are not
stored in the same block, join_results tries to move results so, that they
are stored in one block.
before join_results after join_results
+-------------+ +-------------+
| query 1 | | query 1 |
+-------------+ +-------------+
| table 1 | | table 1 |
+-------------+ +-------------+
| results 1.1 | | free |
+-------------+ +-------------+
| query 2 | | query 2 |
+-------------+ +-------------+
| table 2 | | table 2 |
+-------------+ ---> +-------------+
| results 1.2 | | free |
+-------------+ +-------------+
| results 2 | | results 2 |
+-------------+ +-------------+
| free | | results 1 |
| | | |
| | +-------------+
| | | free |
| | | |
+-------------+ +-------------+
If join_results allocated new block(s) then we need call pack_cache again.
7. Interface
The query cache interfaces with the rest of the server code through 7
functions:
1. Query_cache::send_result_to_client
- Called before parsing and used to match a statement with the stored
queries hash.
If a match is found the cached result set is sent through repeated
calls to net_real_write. (note: calling thread doesn't have a regis-
tered result set writer: thd->net.query_cache_query=0)
2. Query_cache::store_query
- Called just before handle_select() and is used to register a result
set writer to the statement currently being processed
(thd->net.query_cache_query).
3. query_cache_insert
- Called from net_real_write to append a result set to a cached query
if (and only if) this query has a registered result set writer
(thd->net.query_cache_query).
4. Query_cache::invalidate
Query_cache::invalidate_locked_for_write
- Called from various places to invalidate query cache based on data-
base, table and myisam file name. During an on going invalidation
the query cache is temporarily disabled.
5. Query_cache::flush
- Used when a RESET QUERY CACHE is issued. This clears the entire
cache block by block.
6. Query_cache::resize
- Used to change the available memory used by the query cache. This
will also invalidate the entrie query cache in one free operation.
7. Query_cache::pack
- Used when a FLUSH QUERY CACHE is issued. This changes the order of
the used memory blocks in physical memory order and move all avail-
able memory to the 'bottom' of the memory.
TODO list:
- Delayed till after-parsing qache answer (for column rights processing)
- Optimize cache resizing
- if new_size < old_size then pack & shrink
- if new_size > old_size copy cached query to new cache
- Move MRG_MYISAM table type processing to handlers, something like:
tables_used->table->file->register_used_filenames(callback,
first_argument);
- QC improvement suggested by Monty:
- Add a counter in open_table() for how many MERGE (ISAM or MyISAM)
tables are cached in the table cache.
(This will be trivial when we have the new table cache in place I
have been working on)
- After this we can add the following test around the for loop in
is_cacheable::
if (thd->temp_tables || global_merge_table_count)
- Another option would be to set thd->lex->safe_to_cache_query to 0
in 'get_lock_data' if any of the tables was a tmp table or a
MRG_ISAM table.
(This could be done with almost no speed penalty)
*/
#include <my_global.h> /* NO_EMBEDDED_ACCESS_CHECKS */
#include "sql_priv.h"
#include "sql_cache.h"
#include "sql_parse.h" // check_table_access
#include "tztime.h" // struct Time_zone
#include "sql_acl.h" // SELECT_ACL
#include "sql_base.h" // TMP_TABLE_KEY_EXTRA
#include "debug_sync.h" // DEBUG_SYNC
#include "sql_table.h"
#ifdef HAVE_QUERY_CACHE
#include <m_ctype.h>
#include <my_dir.h>
#include <hash.h>
#include "../storage/myisammrg/ha_myisammrg.h"
#include "../storage/myisammrg/myrg_def.h"
#include "probes_mysql.h"
#include "log_slow.h"
#include "transaction.h"
#include "strfunc.h"
const uchar *query_state_map;
#ifdef EMBEDDED_LIBRARY
#include "emb_qcache.h"
#endif
#if defined(EXTRA_DEBUG) && !defined(DBUG_OFF)
#define RW_WLOCK(M) {DBUG_PRINT("lock", ("rwlock wlock 0x%lx",(ulong)(M))); \
if (!mysql_rwlock_wrlock(M)) DBUG_PRINT("lock", ("rwlock wlock ok")); \
else DBUG_PRINT("lock", ("rwlock wlock FAILED %d", errno)); }
#define RW_RLOCK(M) {DBUG_PRINT("lock", ("rwlock rlock 0x%lx", (ulong)(M))); \
if (!mysql_rwlock_rdlock(M)) DBUG_PRINT("lock", ("rwlock rlock ok")); \
else DBUG_PRINT("lock", ("rwlock wlock FAILED %d", errno)); }
#define RW_UNLOCK(M) {DBUG_PRINT("lock", ("rwlock unlock 0x%lx",(ulong)(M))); \
if (!mysql_rwlock_unlock(M)) DBUG_PRINT("lock", ("rwlock unlock ok")); \
else DBUG_PRINT("lock", ("rwlock unlock FAILED %d", errno)); }
#define BLOCK_LOCK_WR(B) {DBUG_PRINT("lock", ("%d LOCK_WR 0x%lx",\
__LINE__,(ulong)(B))); \
B->query()->lock_writing();}
#define BLOCK_LOCK_RD(B) {DBUG_PRINT("lock", ("%d LOCK_RD 0x%lx",\
__LINE__,(ulong)(B))); \
B->query()->lock_reading();}
#define BLOCK_UNLOCK_WR(B) { \
DBUG_PRINT("lock", ("%d UNLOCK_WR 0x%lx",\
__LINE__,(ulong)(B)));B->query()->unlock_writing();}
#define BLOCK_UNLOCK_RD(B) { \
DBUG_PRINT("lock", ("%d UNLOCK_RD 0x%lx",\
__LINE__,(ulong)(B)));B->query()->unlock_reading();}
#define DUMP(C) DBUG_EXECUTE("qcache", {\
(C)->cache_dump(); (C)->queries_dump();(C)->tables_dump();})
#else
#define RW_WLOCK(M) mysql_rwlock_wrlock(M)
#define RW_RLOCK(M) mysql_rwlock_rdlock(M)
#define RW_UNLOCK(M) mysql_rwlock_unlock(M)
#define BLOCK_LOCK_WR(B) B->query()->lock_writing()
#define BLOCK_LOCK_RD(B) B->query()->lock_reading()
#define BLOCK_UNLOCK_WR(B) B->query()->unlock_writing()
#define BLOCK_UNLOCK_RD(B) B->query()->unlock_reading()
#define DUMP(C)
#endif
/**
Macro that executes the requested action at a synchronization point
only if the thread has a associated THD session.
*/
#if defined(ENABLED_DEBUG_SYNC)
#define QC_DEBUG_SYNC(name) \
do { \
THD *thd_tmp= current_thd; \
if (thd_tmp) \
DEBUG_SYNC(thd_tmp, name); \
} while (0)
#else
#define QC_DEBUG_SYNC(name)
#endif
/**
Thread state to be used when the query cache lock needs to be acquired.
Sets the thread state name in the constructor, resets on destructor.
*/
struct Query_cache_wait_state
{
THD *m_thd;
PSI_stage_info m_old_stage;
const char *m_func;
const char *m_file;
int m_line;
Query_cache_wait_state(THD *thd, const char *func,
const char *file, unsigned int line)
: m_thd(thd),
m_old_stage(),
m_func(func), m_file(file), m_line(line)
{
if (m_thd)
set_thd_stage_info(m_thd,
&stage_waiting_for_query_cache_lock,
&m_old_stage,
m_func, m_file, m_line);
}
~Query_cache_wait_state()
{
if (m_thd)
set_thd_stage_info(m_thd, &m_old_stage, NULL, m_func, m_file, m_line);
}
};
/*
Check if character is a white space.
*/
inline bool is_white_space(char c)
{
return (query_state_map[(uint) ((uchar) c)] == MY_LEX_SKIP);
}
/**
Generate a query_string without query comments or duplicated space
@param new_query New query without 'fluff' is stored here
@param query Original query
@param query_length Length of original query
@param additional_length Extra space for query cache we need to allocate
in new_query buffer.
Note:
If there is no space to allocate new_query, we will put original query
into new_query.
*/
static void make_base_query(String *new_query,
const char *query, size_t query_length,
size_t additional_length)
{
char *buffer;
const char *query_end, *last_space;
/* The following is guaranteed by the query_cache interface */
DBUG_ASSERT(query[query_length] == 0);
DBUG_ASSERT(!is_white_space(query[0]));
/* We do not support UCS2, UTF16, UTF32 as a client character set */
DBUG_ASSERT(current_thd->variables.character_set_client->mbminlen == 1);
new_query->length(0); // Don't copy anything from old buffer
if (new_query->realloc(query_length + additional_length))
{
/*
We could not allocate the query. Use original query for
the query cache; Better than nothing....
*/
new_query->set(query, query_length, system_charset_info);
return;
}
buffer= (char*) new_query->ptr(); // Store base query here
query_end= query + query_length;
last_space= 0; // No space found yet
while (query < query_end)
{
char current = *(query++);
switch (current) {
case '\'':
case '`':
case '"':
*(buffer++)= current; // copy first quote
while (query < query_end)
{
*(buffer++)= *query;
if (*(query++) == current) // found pair quote
break;
}
continue; // Continue with next symbol
case '/': // Start of comment ?
/*
Comment of format /#!number #/ or /#M!number #/, must be skipped.
These may include '"' and other comments, but it should
be safe to parse the content as a normal string.
*/
if (query[0] != '*' || query[1] == '!' ||
(query[1] == 'M' && query[2] == '!'))
break;
query++; // skip "/"
while (++query < query_end)
{
if (query[0] == '*' && query[1] == '/')
{
query+= 2;
goto insert_space;
}
}
continue; // Will end outer loop
case '-':
if (*query != '-' || !is_white_space(query[1])) // Not a comment
break;
query++; // skip second "-", and go to search of "\n"
/* fall through */
case '#':
while (query < query_end)
{
if (*(query++) == '\n')
goto insert_space;
}
continue; // Will end outer loop
default:
if (is_white_space(current))
goto insert_space;
break;
}
*(buffer++)= current;
continue;
insert_space:
if (buffer != last_space)
{
*(buffer++)= ' ';
last_space= buffer;
}
}
if (buffer == last_space)
buffer--; // Remove the last space
*buffer= 0; // End zero after query
new_query->length((size_t) (buffer - new_query->ptr()));
/* Copy db_length */
memcpy(buffer+1, query_end+1, QUERY_CACHE_DB_LENGTH_SIZE);
}
/**
Check and change local variable if global one is switched
@param thd thread handle
*/
void inline fix_local_query_cache_mode(THD *thd)
{
if (global_system_variables.query_cache_type == 0)
thd->variables.query_cache_type= 0;
}
/**
Serialize access to the query cache.
If the lock cannot be granted the thread hangs in a conditional wait which
is signalled on each unlock.
The lock attempt will also fail without wait if lock_and_suspend() is in
effect by another thread. This enables a quick path in execution to skip waits
when the outcome is known.
@param mode TIMEOUT the lock can abort because of a timeout
TRY the lock can abort because it is locked now
WAIT wait for lock (default)
@note mode is optional and default value is WAIT.
@return
@retval FALSE An exclusive lock was taken
@retval TRUE The locking attempt failed
*/
bool Query_cache::try_lock(THD *thd, Cache_try_lock_mode mode)
{
bool interrupt= TRUE;
Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
DBUG_ENTER("Query_cache::try_lock");
mysql_mutex_lock(&structure_guard_mutex);
DBUG_EXECUTE_IF("status_wait_query_cache_mutex_sleep", { sleep(5); });
if (m_cache_status == DISABLED)
{
mysql_mutex_unlock(&structure_guard_mutex);
DBUG_RETURN(TRUE);
}
m_requests_in_progress++;
fix_local_query_cache_mode(thd);
while (1)
{
if (m_cache_lock_status == Query_cache::UNLOCKED)
{
m_cache_lock_status= Query_cache::LOCKED;
#ifndef DBUG_OFF
m_cache_lock_thread_id= thd->thread_id;
#endif
interrupt= FALSE;
break;
}
else if (m_cache_lock_status == Query_cache::LOCKED_NO_WAIT)
{
/*
If query cache is protected by a LOCKED_NO_WAIT lock this thread
should avoid using the query cache as it is being evicted.
*/
break;
}
else
{
DBUG_ASSERT(m_cache_lock_status == Query_cache::LOCKED);
/*
To prevent send_result_to_client() and query_cache_insert() from
blocking execution for too long a timeout is put on the lock.
*/
if (mode == WAIT)
{
mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
}
else if (mode == TIMEOUT)
{
struct timespec waittime;
set_timespec_nsec(waittime,(ulong)(50000000L)); /* Wait for 50 msec */
int res= mysql_cond_timedwait(&COND_cache_status_changed,
&structure_guard_mutex, &waittime);
if (res == ETIMEDOUT)
break;
}
else
{
/**
If we are here, then mode is == TRY and there was someone else using
the query cache. (m_cache_lock_status != Query_cache::UNLOCKED).
Signal that we didn't get a lock.
*/
DBUG_ASSERT(m_requests_in_progress > 1);
DBUG_ASSERT(mode == TRY);
break;
}
}
}
if (interrupt)
m_requests_in_progress--;
mysql_mutex_unlock(&structure_guard_mutex);
DBUG_RETURN(interrupt);
}
/**
Serialize access to the query cache.
If the lock cannot be granted the thread hangs in a conditional wait which
is signalled on each unlock.
This method also suspends the query cache so that other threads attempting to
lock the cache with try_lock() will fail directly without waiting.
It is used by all methods which flushes or destroys the whole cache.
*/
void Query_cache::lock_and_suspend(void)
{
THD *thd= current_thd;
Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
DBUG_ENTER("Query_cache::lock_and_suspend");
mysql_mutex_lock(&structure_guard_mutex);
m_requests_in_progress++;
while (m_cache_lock_status != Query_cache::UNLOCKED)
mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
m_cache_lock_status= Query_cache::LOCKED_NO_WAIT;
#ifndef DBUG_OFF
/* Here thd may not be set during shutdown */
if (thd)
m_cache_lock_thread_id= thd->thread_id;
#endif
/* Wake up everybody, a whole cache flush is starting! */
mysql_cond_broadcast(&COND_cache_status_changed);
mysql_mutex_unlock(&structure_guard_mutex);
DBUG_VOID_RETURN;
}
/**
Serialize access to the query cache.
If the lock cannot be granted the thread hangs in a conditional wait which
is signalled on each unlock.
It is used by all methods which invalidates one or more tables.
*/
void Query_cache::lock(THD *thd)
{
Query_cache_wait_state wait_state(thd, __func__, __FILE__, __LINE__);
DBUG_ENTER("Query_cache::lock");
mysql_mutex_lock(&structure_guard_mutex);
m_requests_in_progress++;
fix_local_query_cache_mode(thd);
while (m_cache_lock_status != Query_cache::UNLOCKED)
mysql_cond_wait(&COND_cache_status_changed, &structure_guard_mutex);
m_cache_lock_status= Query_cache::LOCKED;
#ifndef DBUG_OFF
m_cache_lock_thread_id= thd->thread_id;
#endif
mysql_mutex_unlock(&structure_guard_mutex);
DBUG_VOID_RETURN;
}
/**
Set the query cache to UNLOCKED and signal waiting threads.
*/
void Query_cache::unlock(void)
{
DBUG_ENTER("Query_cache::unlock");
mysql_mutex_lock(&structure_guard_mutex);
#ifndef DBUG_OFF
/* Thd may not be set in resize() at mysqld start */
THD *thd= current_thd;
if (thd)
DBUG_ASSERT(m_cache_lock_thread_id == thd->thread_id);
#endif
DBUG_ASSERT(m_cache_lock_status == Query_cache::LOCKED ||
m_cache_lock_status == Query_cache::LOCKED_NO_WAIT);
m_cache_lock_status= Query_cache::UNLOCKED;
DBUG_PRINT("Query_cache",("Sending signal"));
mysql_cond_signal(&COND_cache_status_changed);
DBUG_ASSERT(m_requests_in_progress > 0);
m_requests_in_progress--;
if (m_requests_in_progress == 0 && m_cache_status == DISABLE_REQUEST)
{
/* No clients => just free query cache */
free_cache();
m_cache_status= DISABLED;
}
mysql_mutex_unlock(&structure_guard_mutex);
DBUG_VOID_RETURN;
}
/**
Helper function for determine if a SELECT statement has a SQL_NO_CACHE
directive.
@param sql A pointer to the first white space character after SELECT
@return
@retval TRUE The character string contains SQL_NO_CACHE
@retval FALSE No directive found.
*/
static bool has_no_cache_directive(const char *sql)
{
while (is_white_space(*sql))
sql++;
if (my_toupper(system_charset_info, sql[0]) == 'S' &&
my_toupper(system_charset_info, sql[1]) == 'Q' &&
my_toupper(system_charset_info, sql[2]) == 'L' &&
my_toupper(system_charset_info, sql[3]) == '_' &&
my_toupper(system_charset_info, sql[4]) == 'N' &&
my_toupper(system_charset_info, sql[5]) == 'O' &&
my_toupper(system_charset_info, sql[6]) == '_' &&
my_toupper(system_charset_info, sql[7]) == 'C' &&
my_toupper(system_charset_info, sql[8]) == 'A' &&
my_toupper(system_charset_info, sql[9]) == 'C' &&
my_toupper(system_charset_info, sql[10]) == 'H' &&
my_toupper(system_charset_info, sql[11]) == 'E' &&
my_isspace(system_charset_info, sql[12]))
return TRUE;
return FALSE;
}
/*****************************************************************************
Query_cache_block_table method(s)
*****************************************************************************/
inline Query_cache_block * Query_cache_block_table::block()
{
return (Query_cache_block *)(((uchar*)this) -
ALIGN_SIZE(sizeof(Query_cache_block_table)*n) -
ALIGN_SIZE(sizeof(Query_cache_block)));
}
/*****************************************************************************
Query_cache_block method(s)
*****************************************************************************/
void Query_cache_block::init(ulong block_length)
{
DBUG_ENTER("Query_cache_block::init");
DBUG_PRINT("qcache", ("init block: 0x%lx length: %lu", (ulong) this,
block_length));
length = block_length;
used = 0;
type = Query_cache_block::FREE;
n_tables = 0;
DBUG_VOID_RETURN;
}
void Query_cache_block::destroy()
{
DBUG_ENTER("Query_cache_block::destroy");
DBUG_PRINT("qcache", ("destroy block 0x%lx, type %d",
(ulong) this, type));
type = INCOMPLETE;
DBUG_VOID_RETURN;
}
uint Query_cache_block::headers_len()
{
return (ALIGN_SIZE(sizeof(Query_cache_block_table)*n_tables) +
ALIGN_SIZE(sizeof(Query_cache_block)));
}
uchar* Query_cache_block::data(void)
{
return (uchar*)( ((uchar*)this) + headers_len() );
}
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();
}
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();
}
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();
}
Query_cache_block_table * Query_cache_block::table(TABLE_COUNTER_TYPE n)
{
return ((Query_cache_block_table *)
(((uchar*)this)+ALIGN_SIZE(sizeof(Query_cache_block)) +
n*sizeof(Query_cache_block_table)));
}
/*****************************************************************************
* Query_cache_table method(s)
*****************************************************************************/
extern "C"
{
uchar *query_cache_table_get_key(const uchar *record, size_t *length,
my_bool not_used __attribute__((unused)))
{
Query_cache_block* table_block = (Query_cache_block*) record;
*length = (table_block->used - table_block->headers_len() -
ALIGN_SIZE(sizeof(Query_cache_table)));
return (((uchar *) table_block->data()) +
ALIGN_SIZE(sizeof(Query_cache_table)));
}
}
/*****************************************************************************
Query_cache_query methods
*****************************************************************************/
/*
Following methods work for block read/write locking only in this
particular case and in interaction with structure_guard_mutex.
Lock for write prevents any other locking. (exclusive use)
Lock for read prevents only locking for write.
*/
inline void Query_cache_query::lock_writing()
{
RW_WLOCK(&lock);
}
/*
Needed for finding queries, that we may delete from cache.
We don't want to wait while block become unlocked. In addition,
block locking means that query is now used and we don't need to
remove it.
*/
bool Query_cache_query::try_lock_writing()
{
DBUG_ENTER("Query_cache_block::try_lock_writing");
if (mysql_rwlock_trywrlock(&lock) != 0)
{
DBUG_PRINT("info", ("can't lock rwlock"));
DBUG_RETURN(0);
}
DBUG_PRINT("info", ("rwlock 0x%lx locked", (ulong) &lock));
DBUG_RETURN(1);
}
inline void Query_cache_query::lock_reading()
{
RW_RLOCK(&lock);
}
inline void Query_cache_query::unlock_writing()
{
RW_UNLOCK(&lock);
}
inline void Query_cache_query::unlock_reading()
{
RW_UNLOCK(&lock);
}
void Query_cache_query::init_n_lock()
{
DBUG_ENTER("Query_cache_query::init_n_lock");
res=0; wri = 0; len = 0;
mysql_rwlock_init(key_rwlock_query_cache_query_lock, &lock);
lock_writing();
DBUG_PRINT("qcache", ("inited & locked query for block 0x%lx",
(long) (((uchar*) this) -
ALIGN_SIZE(sizeof(Query_cache_block)))));
DBUG_VOID_RETURN;
}
void Query_cache_query::unlock_n_destroy()
{
DBUG_ENTER("Query_cache_query::unlock_n_destroy");
DBUG_PRINT("qcache", ("destroyed & unlocked query for block 0x%lx",
(long) (((uchar*) this) -
ALIGN_SIZE(sizeof(Query_cache_block)))));
/*
The following call is not needed on system where one can destroy an
active semaphore
*/
this->unlock_writing();
mysql_rwlock_destroy(&lock);
DBUG_VOID_RETURN;
}
extern "C"
{
uchar *query_cache_query_get_key(const uchar *record, size_t *length,
my_bool not_used)
{
Query_cache_block *query_block = (Query_cache_block*) record;
*length = (query_block->used - query_block->headers_len() -
ALIGN_SIZE(sizeof(Query_cache_query)));
return (((uchar *) query_block->data()) +
ALIGN_SIZE(sizeof(Query_cache_query)));
}