MySQL 5.6.14 Source Code Document
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
dict0stats.cc
Go to the documentation of this file.
1 /*****************************************************************************
2 
3 Copyright (c) 2009, 2013, Oracle and/or its affiliates. All Rights Reserved.
4 
5 This program is free software; you can redistribute it and/or modify it under
6 the terms of the GNU General Public License as published by the Free Software
7 Foundation; version 2 of the License.
8 
9 This program is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 
13 You should have received a copy of the GNU General Public License along with
14 this program; if not, write to the Free Software Foundation, Inc.,
15 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
16 
17 *****************************************************************************/
18 
19 /**************************************************/
26 #ifndef UNIV_HOTBACKUP
27 
28 #include "univ.i"
29 
30 #include "btr0btr.h" /* btr_get_size() */
31 #include "btr0cur.h" /* btr_estimate_number_of_different_key_vals() */
32 #include "dict0dict.h" /* dict_table_get_first_index(), dict_fs2utf8() */
33 #include "dict0mem.h" /* DICT_TABLE_MAGIC_N */
34 #include "dict0stats.h"
35 #include "data0type.h" /* dtype_t */
36 #include "db0err.h" /* dberr_t */
37 #include "page0page.h" /* page_align() */
38 #include "pars0pars.h" /* pars_info_create() */
39 #include "pars0types.h" /* pars_info_t */
40 #include "que0que.h" /* que_eval_sql() */
41 #include "rem0cmp.h" /* REC_MAX_N_FIELDS,cmp_rec_rec_with_match() */
42 #include "row0sel.h" /* sel_node_t */
43 #include "row0types.h" /* sel_node_t */
44 #include "trx0trx.h" /* trx_create() */
45 #include "trx0roll.h" /* trx_rollback_to_savepoint() */
46 #include "ut0rnd.h" /* ut_rnd_interval() */
47 #include "ut0ut.h" /* ut_format_name(), ut_time() */
48 
49 #include <vector>
50 
51 /* Sampling algorithm description @{
52 
53 The algorithm is controlled by one number - N_SAMPLE_PAGES(index),
54 let it be A, which is the number of leaf pages to analyze for a given index
55 for each n-prefix (if the index is on 3 columns, then 3*A leaf pages will be
56 analyzed).
57 
58 Let the total number of leaf pages in the table be T.
59 Level 0 - leaf pages, level H - root.
60 
61 Definition: N-prefix-boring record is a record on a non-leaf page that equals
62 the next (to the right, cross page boundaries, skipping the supremum and
63 infimum) record on the same level when looking at the fist n-prefix columns.
64 The last (user) record on a level is not boring (it does not match the
65 non-existent user record to the right). We call the records boring because all
66 the records on the page below a boring record are equal to that boring record.
67 
68 We avoid diving below boring records when searching for a leaf page to
69 estimate the number of distinct records because we know that such a leaf
70 page will have number of distinct records == 1.
71 
72 For each n-prefix: start from the root level and full scan subsequent lower
73 levels until a level that contains at least A*10 distinct records is found.
74 Lets call this level LA.
75 As an optimization the search is canceled if it has reached level 1 (never
76 descend to the level 0 (leaf)) and also if the next level to be scanned
77 would contain more than A pages. The latter is because the user has asked
78 to analyze A leaf pages and it does not make sense to scan much more than
79 A non-leaf pages with the sole purpose of finding a good sample of A leaf
80 pages.
81 
82 After finding the appropriate level LA with >A*10 distinct records (or less in
83 the exceptions described above), divide it into groups of equal records and
84 pick A such groups. Then pick the last record from each group. For example,
85 let the level be:
86 
87 index: 0,1,2,3,4,5,6,7,8,9,10
88 record: 1,1,1,2,2,7,7,7,7,7,9
89 
90 There are 4 groups of distinct records and if A=2 random ones are selected,
91 e.g. 1,1,1 and 7,7,7,7,7, then records with indexes 2 and 9 will be selected.
92 
93 After selecting A records as described above, dive below them to find A leaf
94 pages and analyze them, finding the total number of distinct records. The
95 dive to the leaf level is performed by selecting a non-boring record from
96 each page and diving below it.
97 
98 This way, a total of A leaf pages are analyzed for the given n-prefix.
99 
100 Let the number of different key values found in each leaf page i be Pi (i=1..A).
101 Let N_DIFF_AVG_LEAF be (P1 + P2 + ... + PA) / A.
102 Let the number of different key values on level LA be N_DIFF_LA.
103 Let the total number of records on level LA be TOTAL_LA.
104 Let R be N_DIFF_LA / TOTAL_LA, we assume this ratio is the same on the
105 leaf level.
106 Let the number of leaf pages be N.
107 Then the total number of different key values on the leaf level is:
108 N * R * N_DIFF_AVG_LEAF.
109 See REF01 for the implementation.
110 
111 The above describes how to calculate the cardinality of an index.
112 This algorithm is executed for each n-prefix of a multi-column index
113 where n=1..n_uniq.
114 @} */
115 
116 /* names of the tables from the persistent statistics storage */
117 #define TABLE_STATS_NAME "mysql/innodb_table_stats"
118 #define TABLE_STATS_NAME_PRINT "mysql.innodb_table_stats"
119 #define INDEX_STATS_NAME "mysql/innodb_index_stats"
120 #define INDEX_STATS_NAME_PRINT "mysql.innodb_index_stats"
121 
122 #ifdef UNIV_STATS_DEBUG
123 #define DEBUG_PRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
124 #else /* UNIV_STATS_DEBUG */
125 #define DEBUG_PRINTF(fmt, ...) /* noop */
126 #endif /* UNIV_STATS_DEBUG */
127 
128 /* Gets the number of leaf pages to sample in persistent stats estimation */
129 #define N_SAMPLE_PAGES(index) \
130  ((index)->table->stats_sample_pages != 0 ? \
131  (index)->table->stats_sample_pages : \
132  srv_stats_persistent_sample_pages)
133 
134 /* number of distinct records on a given level that are required to stop
135 descending to lower levels and fetch N_SAMPLE_PAGES(index) records
136 from that level */
137 #define N_DIFF_REQUIRED(index) (N_SAMPLE_PAGES(index) * 10)
138 
139 /* A dynamic array where we store the boundaries of each distinct group
140 of keys. For example if a btree level is:
141 index: 0,1,2,3,4,5,6,7,8,9,10,11,12
142 data: b,b,b,b,b,b,g,g,j,j,j, x, y
143 then we would store 5,7,10,11,12 in the array. */
144 typedef std::vector<ib_uint64_t> boundaries_t;
145 
146 /*********************************************************************/
152 UNIV_INLINE
153 bool
155 /*===========================*/
156  const dict_index_t* index)
157 {
158  return((index->type & DICT_FTS)
159  || dict_index_is_corrupted(index)
160  || index->to_be_dropped
161  || *index->name == TEMP_INDEX_PREFIX);
162 }
163 
164 /*********************************************************************/
168 static
169 bool
170 dict_stats_persistent_storage_check(
171 /*================================*/
172  bool caller_has_dict_sys_mutex)
174 {
175  /* definition for the table TABLE_STATS_NAME */
176  dict_col_meta_t table_stats_columns[] = {
177  {"database_name", DATA_VARMYSQL,
178  DATA_NOT_NULL, 192},
179 
180  {"table_name", DATA_VARMYSQL,
181  DATA_NOT_NULL, 192},
182 
183  {"last_update", DATA_FIXBINARY,
184  DATA_NOT_NULL, 4},
185 
186  {"n_rows", DATA_INT,
187  DATA_NOT_NULL | DATA_UNSIGNED, 8},
188 
189  {"clustered_index_size", DATA_INT,
190  DATA_NOT_NULL | DATA_UNSIGNED, 8},
191 
192  {"sum_of_other_index_sizes", DATA_INT,
193  DATA_NOT_NULL | DATA_UNSIGNED, 8}
194  };
195  dict_table_schema_t table_stats_schema = {
196  TABLE_STATS_NAME,
197  UT_ARR_SIZE(table_stats_columns),
198  table_stats_columns,
199  0 /* n_foreign */,
200  0 /* n_referenced */
201  };
202 
203  /* definition for the table INDEX_STATS_NAME */
204  dict_col_meta_t index_stats_columns[] = {
205  {"database_name", DATA_VARMYSQL,
206  DATA_NOT_NULL, 192},
207 
208  {"table_name", DATA_VARMYSQL,
209  DATA_NOT_NULL, 192},
210 
211  {"index_name", DATA_VARMYSQL,
212  DATA_NOT_NULL, 192},
213 
214  {"last_update", DATA_FIXBINARY,
215  DATA_NOT_NULL, 4},
216 
217  {"stat_name", DATA_VARMYSQL,
218  DATA_NOT_NULL, 64*3},
219 
220  {"stat_value", DATA_INT,
221  DATA_NOT_NULL | DATA_UNSIGNED, 8},
222 
223  {"sample_size", DATA_INT,
224  DATA_UNSIGNED, 8},
225 
226  {"stat_description", DATA_VARMYSQL,
227  DATA_NOT_NULL, 1024*3}
228  };
229  dict_table_schema_t index_stats_schema = {
230  INDEX_STATS_NAME,
231  UT_ARR_SIZE(index_stats_columns),
232  index_stats_columns,
233  0 /* n_foreign */,
234  0 /* n_referenced */
235  };
236 
237  char errstr[512];
238  dberr_t ret;
239 
240  if (!caller_has_dict_sys_mutex) {
241  mutex_enter(&(dict_sys->mutex));
242  }
243 
244  ut_ad(mutex_own(&dict_sys->mutex));
245 
246  /* first check table_stats */
247  ret = dict_table_schema_check(&table_stats_schema, errstr,
248  sizeof(errstr));
249  if (ret == DB_SUCCESS) {
250  /* if it is ok, then check index_stats */
251  ret = dict_table_schema_check(&index_stats_schema, errstr,
252  sizeof(errstr));
253  }
254 
255  if (!caller_has_dict_sys_mutex) {
256  mutex_exit(&(dict_sys->mutex));
257  }
258 
259  if (ret != DB_SUCCESS) {
260  ut_print_timestamp(stderr);
261  fprintf(stderr, " InnoDB: Error: %s\n", errstr);
262  return(false);
263  }
264  /* else */
265 
266  return(true);
267 }
268 
269 /*********************************************************************/
274 static
275 dberr_t
276 dict_stats_exec_sql(
277 /*================*/
278  pars_info_t* pinfo,
280  const char* sql)
281 {
282  trx_t* trx;
283  dberr_t err;
284 
285 #ifdef UNIV_SYNC_DEBUG
286  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
287 #endif /* UNIV_SYNC_DEBUG */
288  ut_ad(mutex_own(&dict_sys->mutex));
289 
290  if (!dict_stats_persistent_storage_check(true)) {
291  pars_info_free(pinfo);
292  return(DB_STATS_DO_NOT_EXIST);
293  }
294 
296  trx_start_if_not_started(trx);
297 
298  err = que_eval_sql(pinfo, sql, FALSE, trx); /* pinfo is freed here */
299 
300  if (err == DB_SUCCESS) {
302  } else {
303  trx->op_info = "rollback of internal trx on stats tables";
304  trx->dict_operation_lock_mode = RW_X_LATCH;
305  trx_rollback_to_savepoint(trx, NULL);
306  trx->dict_operation_lock_mode = 0;
307  trx->op_info = "";
308  ut_a(trx->error_state == DB_SUCCESS);
309  }
310 
312 
313  return(err);
314 }
315 
316 /*********************************************************************/
345 static
347 dict_stats_table_clone_create(
348 /*==========================*/
349  const dict_table_t* table)
350 {
351  size_t heap_size;
353 
354  /* Estimate the size needed for the table and all of its indexes */
355 
356  heap_size = 0;
357  heap_size += sizeof(dict_table_t);
358  heap_size += strlen(table->name) + 1;
359 
360  for (index = dict_table_get_first_index(table);
361  index != NULL;
362  index = dict_table_get_next_index(index)) {
363 
364  if (dict_stats_should_ignore_index(index)) {
365  continue;
366  }
367 
368  ut_ad(!dict_index_is_univ(index));
369 
370  ulint n_uniq = dict_index_get_n_unique(index);
371 
372  heap_size += sizeof(dict_index_t);
373  heap_size += strlen(index->name) + 1;
374  heap_size += n_uniq * sizeof(index->fields[0]);
375  for (ulint i = 0; i < n_uniq; i++) {
376  heap_size += strlen(index->fields[i].name) + 1;
377  }
378  heap_size += n_uniq * sizeof(index->stat_n_diff_key_vals[0]);
379  heap_size += n_uniq * sizeof(index->stat_n_sample_sizes[0]);
380  heap_size += n_uniq * sizeof(index->stat_n_non_null_key_vals[0]);
381  }
382 
383  /* Allocate the memory and copy the members */
384 
385  mem_heap_t* heap;
386 
387  heap = mem_heap_create(heap_size);
388 
389  dict_table_t* t;
390 
391  t = (dict_table_t*) mem_heap_alloc(heap, sizeof(*t));
392 
393  UNIV_MEM_ASSERT_RW_ABORT(&table->id, sizeof(table->id));
394  t->id = table->id;
395 
396  t->heap = heap;
397 
398  UNIV_MEM_ASSERT_RW_ABORT(table->name, strlen(table->name) + 1);
399  t->name = (char*) mem_heap_strdup(heap, table->name);
400 
401  t->corrupted = table->corrupted;
402 
403  UT_LIST_INIT(t->indexes);
404 
405  for (index = dict_table_get_first_index(table);
406  index != NULL;
407  index = dict_table_get_next_index(index)) {
408 
409  if (dict_stats_should_ignore_index(index)) {
410  continue;
411  }
412 
413  ut_ad(!dict_index_is_univ(index));
414 
415  dict_index_t* idx;
416 
417  idx = (dict_index_t*) mem_heap_alloc(heap, sizeof(*idx));
418 
419  UNIV_MEM_ASSERT_RW_ABORT(&index->id, sizeof(index->id));
420  idx->id = index->id;
421 
422  UNIV_MEM_ASSERT_RW_ABORT(index->name, strlen(index->name) + 1);
423  idx->name = (char*) mem_heap_strdup(heap, index->name);
424 
425  idx->table_name = t->name;
426 
427  idx->table = t;
428 
429  idx->type = index->type;
430 
431  idx->to_be_dropped = 0;
432 
434 
435  idx->n_uniq = index->n_uniq;
436 
438  heap, idx->n_uniq * sizeof(idx->fields[0]));
439 
440  for (ulint i = 0; i < idx->n_uniq; i++) {
441  UNIV_MEM_ASSERT_RW_ABORT(index->fields[i].name, strlen(index->fields[i].name) + 1);
442  idx->fields[i].name = (char*) mem_heap_strdup(
443  heap, index->fields[i].name);
444  }
445 
446  /* hook idx into t->indexes */
447  UT_LIST_ADD_LAST(indexes, t->indexes, idx);
448 
449  idx->stat_n_diff_key_vals = (ib_uint64_t*) mem_heap_alloc(
450  heap,
451  idx->n_uniq * sizeof(idx->stat_n_diff_key_vals[0]));
452 
453  idx->stat_n_sample_sizes = (ib_uint64_t*) mem_heap_alloc(
454  heap,
455  idx->n_uniq * sizeof(idx->stat_n_sample_sizes[0]));
456 
457  idx->stat_n_non_null_key_vals = (ib_uint64_t*) mem_heap_alloc(
458  heap,
459  idx->n_uniq * sizeof(idx->stat_n_non_null_key_vals[0]));
460  ut_d(idx->magic_n = DICT_INDEX_MAGIC_N);
461  }
462 
463  ut_d(t->magic_n = DICT_TABLE_MAGIC_N);
464 
465  return(t);
466 }
467 
468 /*********************************************************************/
471 static
472 void
473 dict_stats_table_clone_free(
474 /*========================*/
475  dict_table_t* t)
476 {
477  mem_heap_free(t->heap);
478 }
479 
480 /*********************************************************************/
485 static
486 void
487 dict_stats_empty_index(
488 /*===================*/
489  dict_index_t* index)
490 {
491  ut_ad(!(index->type & DICT_FTS));
492  ut_ad(!dict_index_is_univ(index));
493 
494  ulint n_uniq = index->n_uniq;
495 
496  for (ulint i = 0; i < n_uniq; i++) {
497  index->stat_n_diff_key_vals[i] = 0;
498  index->stat_n_sample_sizes[i] = 1;
499  index->stat_n_non_null_key_vals[i] = 0;
500  }
501 
502  index->stat_index_size = 1;
503  index->stat_n_leaf_pages = 1;
504 }
505 
506 /*********************************************************************/
509 static
510 void
511 dict_stats_empty_table(
512 /*===================*/
513  dict_table_t* table)
514 {
515  /* Zero the stats members */
516 
517  dict_table_stats_lock(table, RW_X_LATCH);
518 
519  table->stat_n_rows = 0;
520  table->stat_clustered_index_size = 1;
521  /* 1 page for each index, not counting the clustered */
523  = UT_LIST_GET_LEN(table->indexes) - 1;
524  table->stat_modified_counter = 0;
525 
527 
528  for (index = dict_table_get_first_index(table);
529  index != NULL;
530  index = dict_table_get_next_index(index)) {
531 
532  if (index->type & DICT_FTS) {
533  continue;
534  }
535 
536  ut_ad(!dict_index_is_univ(index));
537 
538  dict_stats_empty_index(index);
539  }
540 
541  table->stat_initialized = TRUE;
542 
543  dict_table_stats_unlock(table, RW_X_LATCH);
544 }
545 
546 /*********************************************************************/
548 static
549 void
550 dict_stats_assert_initialized_index(
551 /*================================*/
552  const dict_index_t* index)
553 {
554  UNIV_MEM_ASSERT_RW_ABORT(
555  index->stat_n_diff_key_vals,
556  index->n_uniq * sizeof(index->stat_n_diff_key_vals[0]));
557 
558  UNIV_MEM_ASSERT_RW_ABORT(
559  index->stat_n_sample_sizes,
560  index->n_uniq * sizeof(index->stat_n_sample_sizes[0]));
561 
562  UNIV_MEM_ASSERT_RW_ABORT(
563  index->stat_n_non_null_key_vals,
564  index->n_uniq * sizeof(index->stat_n_non_null_key_vals[0]));
565 
566  UNIV_MEM_ASSERT_RW_ABORT(
567  &index->stat_index_size,
568  sizeof(index->stat_index_size));
569 
570  UNIV_MEM_ASSERT_RW_ABORT(
571  &index->stat_n_leaf_pages,
572  sizeof(index->stat_n_leaf_pages));
573 }
574 
575 /*********************************************************************/
577 static
578 void
579 dict_stats_assert_initialized(
580 /*==========================*/
581  const dict_table_t* table)
582 {
583  ut_a(table->stat_initialized);
584 
585  UNIV_MEM_ASSERT_RW_ABORT(&table->stats_last_recalc,
586  sizeof(table->stats_last_recalc));
587 
588  UNIV_MEM_ASSERT_RW_ABORT(&table->stat_persistent,
589  sizeof(table->stat_persistent));
590 
591  UNIV_MEM_ASSERT_RW_ABORT(&table->stats_auto_recalc,
592  sizeof(table->stats_auto_recalc));
593 
594  UNIV_MEM_ASSERT_RW_ABORT(&table->stats_sample_pages,
595  sizeof(table->stats_sample_pages));
596 
597  UNIV_MEM_ASSERT_RW_ABORT(&table->stat_n_rows,
598  sizeof(table->stat_n_rows));
599 
600  UNIV_MEM_ASSERT_RW_ABORT(&table->stat_clustered_index_size,
601  sizeof(table->stat_clustered_index_size));
602 
603  UNIV_MEM_ASSERT_RW_ABORT(&table->stat_sum_of_other_index_sizes,
604  sizeof(table->stat_sum_of_other_index_sizes));
605 
606  UNIV_MEM_ASSERT_RW_ABORT(&table->stat_modified_counter,
607  sizeof(table->stat_modified_counter));
608 
609  UNIV_MEM_ASSERT_RW_ABORT(&table->stats_bg_flag,
610  sizeof(table->stats_bg_flag));
611 
612  for (dict_index_t* index = dict_table_get_first_index(table);
613  index != NULL;
614  index = dict_table_get_next_index(index)) {
615 
616  if (!dict_stats_should_ignore_index(index)) {
617  dict_stats_assert_initialized_index(index);
618  }
619  }
620 }
621 
622 #define INDEX_EQ(i1, i2) \
623  ((i1) != NULL \
624  && (i2) != NULL \
625  && (i1)->id == (i2)->id \
626  && strcmp((i1)->name, (i2)->name) == 0)
627 
628 /*********************************************************************/
632 static
633 void
634 dict_stats_copy(
635 /*============*/
636  dict_table_t* dst,
637  const dict_table_t* src)
638 {
640  dst->stat_n_rows = src->stat_n_rows;
644 
645  dict_index_t* dst_idx;
646  dict_index_t* src_idx;
647 
648  for (dst_idx = dict_table_get_first_index(dst),
649  src_idx = dict_table_get_first_index(src);
650  dst_idx != NULL;
651  dst_idx = dict_table_get_next_index(dst_idx),
652  (src_idx != NULL
653  && (src_idx = dict_table_get_next_index(src_idx)))) {
654 
655  if (dict_stats_should_ignore_index(dst_idx)) {
656  continue;
657  }
658 
659  ut_ad(!dict_index_is_univ(dst_idx));
660 
661  if (!INDEX_EQ(src_idx, dst_idx)) {
662  for (src_idx = dict_table_get_first_index(src);
663  src_idx != NULL;
664  src_idx = dict_table_get_next_index(src_idx)) {
665 
666  if (INDEX_EQ(src_idx, dst_idx)) {
667  break;
668  }
669  }
670  }
671 
672  if (!INDEX_EQ(src_idx, dst_idx)) {
673  dict_stats_empty_index(dst_idx);
674  continue;
675  }
676 
677  ulint n_copy_el;
678 
679  if (dst_idx->n_uniq > src_idx->n_uniq) {
680  n_copy_el = src_idx->n_uniq;
681  /* Since src is smaller some elements in dst
682  will remain untouched by the following memmove(),
683  thus we init all of them here. */
684  dict_stats_empty_index(dst_idx);
685  } else {
686  n_copy_el = dst_idx->n_uniq;
687  }
688 
689  memmove(dst_idx->stat_n_diff_key_vals,
690  src_idx->stat_n_diff_key_vals,
691  n_copy_el * sizeof(dst_idx->stat_n_diff_key_vals[0]));
692 
693  memmove(dst_idx->stat_n_sample_sizes,
694  src_idx->stat_n_sample_sizes,
695  n_copy_el * sizeof(dst_idx->stat_n_sample_sizes[0]));
696 
697  memmove(dst_idx->stat_n_non_null_key_vals,
698  src_idx->stat_n_non_null_key_vals,
699  n_copy_el * sizeof(dst_idx->stat_n_non_null_key_vals[0]));
700 
701  dst_idx->stat_index_size = src_idx->stat_index_size;
702 
703  dst_idx->stat_n_leaf_pages = src_idx->stat_n_leaf_pages;
704  }
705 
706  dst->stat_initialized = TRUE;
707 }
708 
709 /*********************************************************************/
730 static
732 dict_stats_snapshot_create(
733 /*=======================*/
734  const dict_table_t* table)
735 {
736  mutex_enter(&dict_sys->mutex);
737 
738  dict_table_stats_lock(table, RW_S_LATCH);
739 
740  dict_stats_assert_initialized(table);
741 
742  dict_table_t* t;
743 
744  t = dict_stats_table_clone_create(table);
745 
746  dict_stats_copy(t, table);
747 
748  t->stat_persistent = table->stat_persistent;
751  t->stats_bg_flag = table->stats_bg_flag;
752 
753  dict_table_stats_unlock(table, RW_S_LATCH);
754 
755  mutex_exit(&dict_sys->mutex);
756 
757  return(t);
758 }
759 
760 /*********************************************************************/
763 static
764 void
765 dict_stats_snapshot_free(
766 /*=====================*/
767  dict_table_t* t)
768 {
769  dict_stats_table_clone_free(t);
770 }
771 
772 /*********************************************************************/
777 static
778 void
779 dict_stats_update_transient_for_index(
780 /*==================================*/
781  dict_index_t* index)
782 {
783  if (UNIV_LIKELY
786  && dict_index_is_clust(index)))) {
787  mtr_t mtr;
788  ulint size;
789  mtr_start(&mtr);
790  mtr_s_lock(dict_index_get_lock(index), &mtr);
791 
792  size = btr_get_size(index, BTR_TOTAL_SIZE, &mtr);
793 
794  if (size != ULINT_UNDEFINED) {
795  index->stat_index_size = size;
796 
797  size = btr_get_size(
798  index, BTR_N_LEAF_PAGES, &mtr);
799  }
800 
801  mtr_commit(&mtr);
802 
803  switch (size) {
804  case ULINT_UNDEFINED:
805  dict_stats_empty_index(index);
806  return;
807  case 0:
808  /* The root node of the tree is a leaf */
809  size = 1;
810  }
811 
812  index->stat_n_leaf_pages = size;
813 
815  } else {
816  /* If we have set a high innodb_force_recovery
817  level, do not calculate statistics, as a badly
818  corrupted index can cause a crash in it.
819  Initialize some bogus index cardinality
820  statistics, so that the data can be queried in
821  various means, also via secondary indexes. */
822  dict_stats_empty_index(index);
823  }
824 }
825 
826 /*********************************************************************/
832 UNIV_INTERN
833 void
835 /*========================*/
836  dict_table_t* table)
837 {
839  ulint sum_of_index_sizes = 0;
840 
841  /* Find out the sizes of the indexes and how many different values
842  for the key they approximately have */
843 
844  index = dict_table_get_first_index(table);
845 
846  if (dict_table_is_discarded(table)) {
847  /* Nothing to do. */
848  dict_stats_empty_table(table);
849  return;
850  } else if (index == NULL) {
851  /* Table definition is corrupt */
852 
853  char buf[MAX_FULL_NAME_LEN];
854  ut_print_timestamp(stderr);
855  fprintf(stderr, " InnoDB: table %s has no indexes. "
856  "Cannot calculate statistics.\n",
857  ut_format_name(table->name, TRUE, buf, sizeof(buf)));
858  dict_stats_empty_table(table);
859  return;
860  }
861 
862  for (; index != NULL; index = dict_table_get_next_index(index)) {
863 
864  ut_ad(!dict_index_is_univ(index));
865 
866  if (index->type & DICT_FTS) {
867  continue;
868  }
869 
870  dict_stats_empty_index(index);
871 
872  if (dict_stats_should_ignore_index(index)) {
873  continue;
874  }
875 
876  dict_stats_update_transient_for_index(index);
877 
878  sum_of_index_sizes += index->stat_index_size;
879  }
880 
881  index = dict_table_get_first_index(table);
882 
883  table->stat_n_rows = index->stat_n_diff_key_vals[
884  dict_index_get_n_unique(index) - 1];
885 
887 
888  table->stat_sum_of_other_index_sizes = sum_of_index_sizes
889  - index->stat_index_size;
890 
891  table->stats_last_recalc = ut_time();
892 
893  table->stat_modified_counter = 0;
894 
895  table->stat_initialized = TRUE;
896 }
897 
898 /* @{ Pseudo code about the relation between the following functions
899 
900 let N = N_SAMPLE_PAGES(index)
901 
902 dict_stats_analyze_index()
903  for each n_prefix
904  search for good enough level:
905  dict_stats_analyze_index_level() // only called if level has <= N pages
906  // full scan of the level in one mtr
907  collect statistics about the given level
908  if we are not satisfied with the level, search next lower level
909  we have found a good enough level here
910  dict_stats_analyze_index_for_n_prefix(that level, stats collected above)
911  // full scan of the level in one mtr
912  dive below some records and analyze the leaf page there:
913  dict_stats_analyze_index_below_cur()
914 @} */
915 
916 /*********************************************************************/
924 static
925 void
926 dict_stats_analyze_index_level(
927 /*===========================*/
928  dict_index_t* index,
929  ulint level,
930  ib_uint64_t* n_diff,
932  ib_uint64_t* total_recs,
933  ib_uint64_t* total_pages,
934  boundaries_t* n_diff_boundaries,
936  mtr_t* mtr)
937 {
938  ulint n_uniq;
939  mem_heap_t* heap;
941  const page_t* page;
942  const rec_t* rec;
943  const rec_t* prev_rec;
944  bool prev_rec_is_copied;
945  byte* prev_rec_buf = NULL;
946  ulint prev_rec_buf_size = 0;
947  ulint* rec_offsets;
948  ulint* prev_rec_offsets;
949  ulint i;
950 
951  DEBUG_PRINTF(" %s(table=%s, index=%s, level=%lu)\n", __func__,
952  index->table->name, index->name, level);
953 
954  ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index),
955  MTR_MEMO_S_LOCK));
956 
957  n_uniq = dict_index_get_n_unique(index);
958 
959  /* elements in the n_diff array are 0..n_uniq-1 (inclusive) */
960  memset(n_diff, 0x0, n_uniq * sizeof(n_diff[0]));
961 
962  /* Allocate space for the offsets header (the allocation size at
963  offsets[0] and the REC_OFFS_HEADER_SIZE bytes), and n_fields + 1,
964  so that this will never be less than the size calculated in
965  rec_get_offsets_func(). */
966  i = (REC_OFFS_HEADER_SIZE + 1 + 1) + index->n_fields;
967 
968  heap = mem_heap_create((2 * sizeof *rec_offsets) * i);
969  rec_offsets = static_cast<ulint*>(
970  mem_heap_alloc(heap, i * sizeof *rec_offsets));
971  prev_rec_offsets = static_cast<ulint*>(
972  mem_heap_alloc(heap, i * sizeof *prev_rec_offsets));
973  rec_offs_set_n_alloc(rec_offsets, i);
974  rec_offs_set_n_alloc(prev_rec_offsets, i);
975 
976  /* reset the dynamic arrays n_diff_boundaries[0..n_uniq-1] */
977  if (n_diff_boundaries != NULL) {
978  for (i = 0; i < n_uniq; i++) {
979  n_diff_boundaries[i].erase(
980  n_diff_boundaries[i].begin(),
981  n_diff_boundaries[i].end());
982  }
983  }
984 
985  /* Position pcur on the leftmost record on the leftmost page
986  on the desired level. */
987 
989  true, index, BTR_SEARCH_LEAF | BTR_ALREADY_S_LATCHED,
990  &pcur, true, level, mtr);
992 
993  page = btr_pcur_get_page(&pcur);
994 
995  /* The page must not be empty, except when
996  it is the root page (and the whole index is empty). */
997  ut_ad(btr_pcur_is_on_user_rec(&pcur) || page_is_leaf(page));
998  ut_ad(btr_pcur_get_rec(&pcur)
999  == page_rec_get_next_const(page_get_infimum_rec(page)));
1000 
1001  /* check that we are indeed on the desired level */
1002  ut_a(btr_page_get_level(page, mtr) == level);
1003 
1004  /* there should not be any pages on the left */
1005  ut_a(btr_page_get_prev(page, mtr) == FIL_NULL);
1006 
1007  /* check whether the first record on the leftmost page is marked
1008  as such, if we are on a non-leaf level */
1009  ut_a((level == 0)
1010  == !(REC_INFO_MIN_REC_FLAG & rec_get_info_bits(
1011  btr_pcur_get_rec(&pcur), page_is_comp(page))));
1012 
1013  prev_rec = NULL;
1014  prev_rec_is_copied = false;
1015 
1016  /* no records by default */
1017  *total_recs = 0;
1018 
1019  *total_pages = 0;
1020 
1021  /* iterate over all user records on this level
1022  and compare each two adjacent ones, even the last on page
1023  X and the fist on page X+1 */
1024  for (;
1025  btr_pcur_is_on_user_rec(&pcur);
1026  btr_pcur_move_to_next_user_rec(&pcur, mtr)) {
1027 
1028  ulint matched_fields = 0;
1029  ulint matched_bytes = 0;
1030  bool rec_is_last_on_page;
1031 
1032  rec = btr_pcur_get_rec(&pcur);
1033 
1034  /* If rec and prev_rec are on different pages, then prev_rec
1035  must have been copied, because we hold latch only on the page
1036  where rec resides. */
1037  if (prev_rec != NULL
1038  && page_align(rec) != page_align(prev_rec)) {
1039 
1040  ut_a(prev_rec_is_copied);
1041  }
1042 
1043  rec_is_last_on_page =
1045 
1046  /* increment the pages counter at the end of each page */
1047  if (rec_is_last_on_page) {
1048 
1049  (*total_pages)++;
1050  }
1051 
1052  /* Skip delete-marked records on the leaf level. If we
1053  do not skip them, then ANALYZE quickly after DELETE
1054  could count them or not (purge may have already wiped
1055  them away) which brings non-determinism. We skip only
1056  leaf-level delete marks because delete marks on
1057  non-leaf level do not make sense. */
1058  if (level == 0 &&
1060  rec,
1061  page_is_comp(btr_pcur_get_page(&pcur)))) {
1062 
1063  if (rec_is_last_on_page
1064  && !prev_rec_is_copied
1065  && prev_rec != NULL) {
1066  /* copy prev_rec */
1067 
1068  prev_rec_offsets = rec_get_offsets(
1069  prev_rec, index, prev_rec_offsets,
1070  n_uniq, &heap);
1071 
1072  prev_rec = rec_copy_prefix_to_buf(
1073  prev_rec, index,
1074  rec_offs_n_fields(prev_rec_offsets),
1075  &prev_rec_buf, &prev_rec_buf_size);
1076 
1077  prev_rec_is_copied = true;
1078  }
1079 
1080  continue;
1081  }
1082 
1083  rec_offsets = rec_get_offsets(
1084  rec, index, rec_offsets, n_uniq, &heap);
1085 
1086  (*total_recs)++;
1087 
1088  if (prev_rec != NULL) {
1089  prev_rec_offsets = rec_get_offsets(
1090  prev_rec, index, prev_rec_offsets,
1091  n_uniq, &heap);
1092 
1094  prev_rec,
1095  rec_offsets,
1096  prev_rec_offsets,
1097  index,
1098  FALSE,
1099  &matched_fields,
1100  &matched_bytes);
1101 
1102  for (i = matched_fields; i < n_uniq; i++) {
1103 
1104  if (n_diff_boundaries != NULL) {
1105  /* push the index of the previous
1106  record, that is - the last one from
1107  a group of equal keys */
1108 
1109  ib_uint64_t idx;
1110 
1111  /* the index of the current record
1112  is total_recs - 1, the index of the
1113  previous record is total_recs - 2;
1114  we know that idx is not going to
1115  become negative here because if we
1116  are in this branch then there is a
1117  previous record and thus
1118  total_recs >= 2 */
1119  idx = *total_recs - 2;
1120 
1121  n_diff_boundaries[i].push_back(idx);
1122  }
1123 
1124  /* increment the number of different keys
1125  for n_prefix=i+1 (e.g. if i=0 then we increment
1126  for n_prefix=1 which is stored in n_diff[0]) */
1127  n_diff[i]++;
1128  }
1129  } else {
1130  /* this is the first non-delete marked record */
1131  for (i = 0; i < n_uniq; i++) {
1132  n_diff[i] = 1;
1133  }
1134  }
1135 
1136  if (rec_is_last_on_page) {
1137  /* end of a page has been reached */
1138 
1139  /* we need to copy the record instead of assigning
1140  like prev_rec = rec; because when we traverse the
1141  records on this level at some point we will jump from
1142  one page to the next and then rec and prev_rec will
1143  be on different pages and
1144  btr_pcur_move_to_next_user_rec() will release the
1145  latch on the page that prev_rec is on */
1146  prev_rec = rec_copy_prefix_to_buf(
1147  rec, index, rec_offs_n_fields(rec_offsets),
1148  &prev_rec_buf, &prev_rec_buf_size);
1149  prev_rec_is_copied = true;
1150 
1151  } else {
1152  /* still on the same page, the next call to
1153  btr_pcur_move_to_next_user_rec() will not jump
1154  on the next page, we can simply assign pointers
1155  instead of copying the records like above */
1156 
1157  prev_rec = rec;
1158  prev_rec_is_copied = false;
1159  }
1160  }
1161 
1162  /* if *total_pages is left untouched then the above loop was not
1163  entered at all and there is one page in the whole tree which is
1164  empty or the loop was entered but this is level 0, contains one page
1165  and all records are delete-marked */
1166  if (*total_pages == 0) {
1167 
1168  ut_ad(level == 0);
1169  ut_ad(*total_recs == 0);
1170 
1171  *total_pages = 1;
1172  }
1173 
1174  /* if there are records on this level and boundaries
1175  should be saved */
1176  if (*total_recs > 0 && n_diff_boundaries != NULL) {
1177 
1178  /* remember the index of the last record on the level as the
1179  last one from the last group of equal keys; this holds for
1180  all possible prefixes */
1181  for (i = 0; i < n_uniq; i++) {
1182  ib_uint64_t idx;
1183 
1184  idx = *total_recs - 1;
1185 
1186  n_diff_boundaries[i].push_back(idx);
1187  }
1188  }
1189 
1190  /* now in n_diff_boundaries[i] there are exactly n_diff[i] integers,
1191  for i=0..n_uniq-1 */
1192 
1193 #ifdef UNIV_STATS_DEBUG
1194  for (i = 0; i < n_uniq; i++) {
1195 
1196  DEBUG_PRINTF(" %s(): total recs: " UINT64PF
1197  ", total pages: " UINT64PF
1198  ", n_diff[%lu]: " UINT64PF "\n",
1199  __func__, *total_recs,
1200  *total_pages,
1201  i, n_diff[i]);
1202 
1203 #if 0
1204  if (n_diff_boundaries != NULL) {
1205  ib_uint64_t j;
1206 
1207  DEBUG_PRINTF(" %s(): boundaries[%lu]: ",
1208  __func__, i);
1209 
1210  for (j = 0; j < n_diff[i]; j++) {
1211  ib_uint64_t idx;
1212 
1213  idx = n_diff_boundaries[i][j];
1214 
1215  DEBUG_PRINTF(UINT64PF "=" UINT64PF ", ",
1216  j, idx);
1217  }
1218  DEBUG_PRINTF("\n");
1219  }
1220 #endif
1221  }
1222 #endif /* UNIV_STATS_DEBUG */
1223 
1224  /* Release the latch on the last page, because that is not done by
1225  btr_pcur_close(). This function works also for non-leaf pages. */
1226  btr_leaf_page_release(btr_pcur_get_block(&pcur), BTR_SEARCH_LEAF, mtr);
1227 
1228  btr_pcur_close(&pcur);
1229 
1230  if (prev_rec_buf != NULL) {
1231 
1232  mem_free(prev_rec_buf);
1233  }
1234 
1235  mem_heap_free(heap);
1236 }
1237 
1238 /* aux enum for controlling the behavior of dict_stats_scan_page() @{ */
1239 enum page_scan_method_t {
1240  COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED,/* scan all records on
1241  the given page and count the number of
1242  distinct ones, also ignore delete marked
1243  records */
1244  QUIT_ON_FIRST_NON_BORING/* quit when the first record that differs
1245  from its right neighbor is found */
1246 };
1247 /* @} */
1248 
1249 /*********************************************************************/
1259 UNIV_INLINE __attribute__((nonnull))
1260 ulint*
1261 dict_stats_scan_page(
1262 /*=================*/
1263  const rec_t** out_rec,
1264  ulint* offsets1,
1267  ulint* offsets2,
1270  dict_index_t* index,
1271  const page_t* page,
1272  ulint n_prefix,
1274  page_scan_method_t scan_method,
1276  ib_uint64_t* n_diff)
1278 {
1279  ulint* offsets_rec = offsets1;
1280  ulint* offsets_next_rec = offsets2;
1281  const rec_t* rec;
1282  const rec_t* next_rec;
1283  /* A dummy heap, to be passed to rec_get_offsets().
1284  Because offsets1,offsets2 should be big enough,
1285  this memory heap should never be used. */
1286  mem_heap_t* heap = NULL;
1287  const rec_t* (*get_next)(const rec_t*);
1288 
1289  if (scan_method == COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED) {
1291  } else {
1292  get_next = page_rec_get_next_const;
1293  }
1294 
1295  rec = get_next(page_get_infimum_rec(page));
1296 
1297  if (page_rec_is_supremum(rec)) {
1298  /* the page is empty or contains only delete-marked records */
1299  *n_diff = 0;
1300  *out_rec = NULL;
1301  return(NULL);
1302  }
1303 
1304  offsets_rec = rec_get_offsets(rec, index, offsets_rec,
1305  ULINT_UNDEFINED, &heap);
1306 
1307  next_rec = get_next(rec);
1308 
1309  *n_diff = 1;
1310 
1311  while (!page_rec_is_supremum(next_rec)) {
1312 
1313  ulint matched_fields = 0;
1314  ulint matched_bytes = 0;
1315 
1316  offsets_next_rec = rec_get_offsets(next_rec, index,
1317  offsets_next_rec,
1318  ULINT_UNDEFINED,
1319  &heap);
1320 
1321  /* check whether rec != next_rec when looking at
1322  the first n_prefix fields */
1323  cmp_rec_rec_with_match(rec, next_rec,
1324  offsets_rec, offsets_next_rec,
1325  index, FALSE, &matched_fields,
1326  &matched_bytes);
1327 
1328  if (matched_fields < n_prefix) {
1329  /* rec != next_rec, => rec is non-boring */
1330 
1331  (*n_diff)++;
1332 
1333  if (scan_method == QUIT_ON_FIRST_NON_BORING) {
1334  goto func_exit;
1335  }
1336  }
1337 
1338  rec = next_rec;
1339  {
1340  /* Assign offsets_rec = offsets_next_rec
1341  so that offsets_rec matches with rec which
1342  was just assigned rec = next_rec above.
1343  Also need to point offsets_next_rec to the
1344  place where offsets_rec was pointing before
1345  because we have just 2 placeholders where
1346  data is actually stored:
1347  offsets_onstack1 and offsets_onstack2 and we
1348  are using them in circular fashion
1349  (offsets[_next]_rec are just pointers to
1350  those placeholders). */
1351  ulint* offsets_tmp;
1352  offsets_tmp = offsets_rec;
1353  offsets_rec = offsets_next_rec;
1354  offsets_next_rec = offsets_tmp;
1355  }
1356 
1357  next_rec = get_next(next_rec);
1358  }
1359 
1360 func_exit:
1361  /* offsets1,offsets2 should have been big enough */
1362  ut_a(heap == NULL);
1363  *out_rec = rec;
1364  return(offsets_rec);
1365 }
1366 
1367 /*********************************************************************/
1372 static
1373 ib_uint64_t
1374 dict_stats_analyze_index_below_cur(
1375 /*===============================*/
1376  const btr_cur_t*cur,
1377  ulint n_prefix,
1379  mtr_t* mtr)
1380 {
1382  ulint space;
1383  ulint zip_size;
1384  buf_block_t* block;
1385  ulint page_no;
1386  const page_t* page;
1387  mem_heap_t* heap;
1388  const rec_t* rec;
1389  ulint* offsets1;
1390  ulint* offsets2;
1391  ulint* offsets_rec;
1392  ib_uint64_t n_diff; /* the result */
1393  ulint size;
1394 
1395  index = btr_cur_get_index(cur);
1396 
1397  /* Allocate offsets for the record and the node pointer, for
1398  node pointer records. In a secondary index, the node pointer
1399  record will consist of all index fields followed by a child
1400  page number.
1401  Allocate space for the offsets header (the allocation size at
1402  offsets[0] and the REC_OFFS_HEADER_SIZE bytes), and n_fields + 1,
1403  so that this will never be less than the size calculated in
1404  rec_get_offsets_func(). */
1405  size = (1 + REC_OFFS_HEADER_SIZE) + 1 + dict_index_get_n_fields(index);
1406 
1407  heap = mem_heap_create(size * (sizeof *offsets1 + sizeof *offsets2));
1408 
1409  offsets1 = static_cast<ulint*>(mem_heap_alloc(
1410  heap, size * sizeof *offsets1));
1411 
1412  offsets2 = static_cast<ulint*>(mem_heap_alloc(
1413  heap, size * sizeof *offsets2));
1414 
1415  rec_offs_set_n_alloc(offsets1, size);
1416  rec_offs_set_n_alloc(offsets2, size);
1417 
1418  space = dict_index_get_space(index);
1419  zip_size = dict_table_zip_size(index->table);
1420 
1421  rec = btr_cur_get_rec(cur);
1422 
1423  offsets_rec = rec_get_offsets(rec, index, offsets1,
1424  ULINT_UNDEFINED, &heap);
1425 
1426  page_no = btr_node_ptr_get_child_page_no(rec, offsets_rec);
1427 
1428  /* descend to the leaf level on the B-tree */
1429  for (;;) {
1430 
1431  block = buf_page_get_gen(space, zip_size, page_no, RW_S_LATCH,
1432  NULL /* no guessed block */,
1433  BUF_GET, __FILE__, __LINE__, mtr);
1434 
1435  page = buf_block_get_frame(block);
1436 
1437  if (btr_page_get_level(page, mtr) == 0) {
1438  /* leaf level */
1439  break;
1440  }
1441  /* else */
1442 
1443  /* search for the first non-boring record on the page */
1444  offsets_rec = dict_stats_scan_page(
1445  &rec, offsets1, offsets2, index, page, n_prefix,
1446  QUIT_ON_FIRST_NON_BORING, &n_diff);
1447 
1448  /* pages on level > 0 are not allowed to be empty */
1449  ut_a(offsets_rec != NULL);
1450  /* if page is not empty (offsets_rec != NULL) then n_diff must
1451  be > 0, otherwise there is a bug in dict_stats_scan_page() */
1452  ut_a(n_diff > 0);
1453 
1454  if (n_diff == 1) {
1455  /* page has all keys equal and the end of the page
1456  was reached by dict_stats_scan_page(), no need to
1457  descend to the leaf level */
1458  mem_heap_free(heap);
1459  return(1);
1460  }
1461  /* else */
1462 
1463  /* when we instruct dict_stats_scan_page() to quit on the
1464  first non-boring record it finds, then the returned n_diff
1465  can either be 0 (empty page), 1 (page has all keys equal) or
1466  2 (non-boring record was found) */
1467  ut_a(n_diff == 2);
1468 
1469  /* we have a non-boring record in rec, descend below it */
1470 
1471  page_no = btr_node_ptr_get_child_page_no(rec, offsets_rec);
1472  }
1473 
1474  /* make sure we got a leaf page as a result from the above loop */
1475  ut_ad(btr_page_get_level(page, mtr) == 0);
1476 
1477  /* scan the leaf page and find the number of distinct keys,
1478  when looking only at the first n_prefix columns */
1479 
1480  offsets_rec = dict_stats_scan_page(
1481  &rec, offsets1, offsets2, index, page, n_prefix,
1482  COUNT_ALL_NON_BORING_AND_SKIP_DEL_MARKED, &n_diff);
1483 
1484 #if 0
1485  DEBUG_PRINTF(" %s(): n_diff below page_no=%lu: " UINT64PF "\n",
1486  __func__, page_no, n_diff);
1487 #endif
1488 
1489  mem_heap_free(heap);
1490 
1491  return(n_diff);
1492 }
1493 
1494 /*********************************************************************/
1500 static
1501 void
1502 dict_stats_analyze_index_for_n_prefix(
1503 /*==================================*/
1504  dict_index_t* index,
1505  ulint level,
1506  ib_uint64_t total_recs_on_level,
1509  ulint n_prefix,
1512  ib_uint64_t n_diff_for_this_prefix,
1517  boundaries_t* boundaries,
1526  mtr_t* mtr)
1527 {
1528  btr_pcur_t pcur;
1529  const page_t* page;
1530  ib_uint64_t rec_idx;
1531  ib_uint64_t last_idx_on_level;
1532  ib_uint64_t n_recs_to_dive_below;
1533  ib_uint64_t n_diff_sum_of_all_analyzed_pages;
1534  ib_uint64_t i;
1535 
1536 #if 0
1537  DEBUG_PRINTF(" %s(table=%s, index=%s, level=%lu, n_prefix=%lu, "
1538  "n_diff_for_this_prefix=" UINT64PF ")\n",
1539  __func__, index->table->name, index->name, level,
1540  n_prefix, n_diff_for_this_prefix);
1541 #endif
1542 
1543  ut_ad(mtr_memo_contains(mtr, dict_index_get_lock(index),
1544  MTR_MEMO_S_LOCK));
1545 
1546  /* if some of those is 0 then this means that there is exactly one
1547  page in the B-tree and it is empty and we should have done full scan
1548  and should not be here */
1549  ut_ad(total_recs_on_level > 0);
1550  ut_ad(n_diff_for_this_prefix > 0);
1551 
1552  /* this must be at least 1 */
1553  ut_ad(N_SAMPLE_PAGES(index) > 0);
1554 
1555  /* Position pcur on the leftmost record on the leftmost page
1556  on the desired level. */
1557 
1559  true, index, BTR_SEARCH_LEAF | BTR_ALREADY_S_LATCHED,
1560  &pcur, true, level, mtr);
1562 
1563  page = btr_pcur_get_page(&pcur);
1564 
1565  /* The page must not be empty, except when
1566  it is the root page (and the whole index is empty). */
1567  ut_ad(btr_pcur_is_on_user_rec(&pcur) || page_is_leaf(page));
1568  ut_ad(btr_pcur_get_rec(&pcur)
1569  == page_rec_get_next_const(page_get_infimum_rec(page)));
1570 
1571  /* check that we are indeed on the desired level */
1572  ut_a(btr_page_get_level(page, mtr) == level);
1573 
1574  /* there should not be any pages on the left */
1575  ut_a(btr_page_get_prev(page, mtr) == FIL_NULL);
1576 
1577  /* check whether the first record on the leftmost page is marked
1578  as such, if we are on a non-leaf level */
1579  ut_a((level == 0)
1580  == !(REC_INFO_MIN_REC_FLAG & rec_get_info_bits(
1581  btr_pcur_get_rec(&pcur), page_is_comp(page))));
1582 
1583  last_idx_on_level = boundaries->at(n_diff_for_this_prefix - 1);
1584 
1585  rec_idx = 0;
1586 
1587  n_diff_sum_of_all_analyzed_pages = 0;
1588 
1589  n_recs_to_dive_below = ut_min(N_SAMPLE_PAGES(index),
1590  n_diff_for_this_prefix);
1591 
1592  for (i = 0; i < n_recs_to_dive_below; i++) {
1593  ib_uint64_t left;
1594  ib_uint64_t right;
1595  ulint rnd;
1596  ib_uint64_t dive_below_idx;
1597 
1598  /* there are n_diff_for_this_prefix elements
1599  in 'boundaries' and we divide those elements
1600  into n_recs_to_dive_below segments, for example:
1601 
1602  let n_diff_for_this_prefix=100, n_recs_to_dive_below=4, then:
1603  segment i=0: [0, 24]
1604  segment i=1: [25, 49]
1605  segment i=2: [50, 74]
1606  segment i=3: [75, 99] or
1607 
1608  let n_diff_for_this_prefix=1, n_recs_to_dive_below=1, then:
1609  segment i=0: [0, 0] or
1610 
1611  let n_diff_for_this_prefix=2, n_recs_to_dive_below=2, then:
1612  segment i=0: [0, 0]
1613  segment i=1: [1, 1] or
1614 
1615  let n_diff_for_this_prefix=13, n_recs_to_dive_below=7, then:
1616  segment i=0: [0, 0]
1617  segment i=1: [1, 2]
1618  segment i=2: [3, 4]
1619  segment i=3: [5, 6]
1620  segment i=4: [7, 8]
1621  segment i=5: [9, 10]
1622  segment i=6: [11, 12]
1623 
1624  then we select a random record from each segment and dive
1625  below it */
1626  left = n_diff_for_this_prefix * i / n_recs_to_dive_below;
1627  right = n_diff_for_this_prefix * (i + 1)
1628  / n_recs_to_dive_below - 1;
1629 
1630  ut_a(left <= right);
1631  ut_a(right <= last_idx_on_level);
1632 
1633  /* we do not pass (left, right) because we do not want to ask
1634  ut_rnd_interval() to work with too big numbers since
1635  ib_uint64_t could be bigger than ulint */
1636  rnd = ut_rnd_interval(0, (ulint) (right - left));
1637 
1638  dive_below_idx = boundaries->at(left + rnd);
1639 
1640 #if 0
1641  DEBUG_PRINTF(" %s(): dive below record with index="
1642  UINT64PF "\n", __func__, dive_below_idx);
1643 #endif
1644 
1645  /* seek to the record with index dive_below_idx */
1646  while (rec_idx < dive_below_idx
1647  && btr_pcur_is_on_user_rec(&pcur)) {
1648 
1649  btr_pcur_move_to_next_user_rec(&pcur, mtr);
1650  rec_idx++;
1651  }
1652 
1653  /* if the level has finished before the record we are
1654  searching for, this means that the B-tree has changed in
1655  the meantime, quit our sampling and use whatever stats
1656  we have collected so far */
1657  if (rec_idx < dive_below_idx) {
1658 
1659  ut_ad(!btr_pcur_is_on_user_rec(&pcur));
1660  break;
1661  }
1662 
1663  /* it could be that the tree has changed in such a way that
1664  the record under dive_below_idx is the supremum record, in
1665  this case rec_idx == dive_below_idx and pcur is positioned
1666  on the supremum, we do not want to dive below it */
1667  if (!btr_pcur_is_on_user_rec(&pcur)) {
1668  break;
1669  }
1670 
1671  ut_a(rec_idx == dive_below_idx);
1672 
1673  ib_uint64_t n_diff_on_leaf_page;
1674 
1675  n_diff_on_leaf_page = dict_stats_analyze_index_below_cur(
1676  btr_pcur_get_btr_cur(&pcur), n_prefix, mtr);
1677 
1678  /* We adjust n_diff_on_leaf_page here to avoid counting
1679  one record twice - once as the last on some page and once
1680  as the first on another page. Consider the following example:
1681  Leaf level:
1682  page: (2,2,2,2,3,3)
1683  ... many pages like (3,3,3,3,3,3) ...
1684  page: (3,3,3,3,5,5)
1685  ... many pages like (5,5,5,5,5,5) ...
1686  page: (5,5,5,5,8,8)
1687  page: (8,8,8,8,9,9)
1688  our algo would (correctly) get an estimate that there are
1689  2 distinct records per page (average). Having 4 pages below
1690  non-boring records, it would (wrongly) estimate the number
1691  of distinct records to 8. */
1692  if (n_diff_on_leaf_page > 0) {
1693  n_diff_on_leaf_page--;
1694  }
1695 
1696  n_diff_sum_of_all_analyzed_pages += n_diff_on_leaf_page;
1697  }
1698 
1699  /* n_diff_sum_of_all_analyzed_pages can be 0 here if all the leaf
1700  pages sampled contained only delete-marked records. In this case
1701  we should assign 0 to index->stat_n_diff_key_vals[n_prefix - 1], which
1702  the formula below does. */
1703 
1704  /* See REF01 for an explanation of the algorithm */
1705  index->stat_n_diff_key_vals[n_prefix - 1]
1706  = index->stat_n_leaf_pages
1707 
1708  * n_diff_for_this_prefix
1709  / total_recs_on_level
1710 
1711  * n_diff_sum_of_all_analyzed_pages
1712  / n_recs_to_dive_below;
1713 
1714  index->stat_n_sample_sizes[n_prefix - 1] = n_recs_to_dive_below;
1715 
1716  DEBUG_PRINTF(" %s(): n_diff=" UINT64PF " for n_prefix=%lu "
1717  "(%lu"
1718  " * " UINT64PF " / " UINT64PF
1719  " * " UINT64PF " / " UINT64PF ")\n",
1720  __func__, index->stat_n_diff_key_vals[n_prefix - 1],
1721  n_prefix,
1722  index->stat_n_leaf_pages,
1723  n_diff_for_this_prefix, total_recs_on_level,
1724  n_diff_sum_of_all_analyzed_pages, n_recs_to_dive_below);
1725 
1726  btr_pcur_close(&pcur);
1727 }
1728 
1729 /*********************************************************************/
1733 static
1734 void
1735 dict_stats_analyze_index(
1736 /*=====================*/
1737  dict_index_t* index)
1738 {
1739  ulint root_level;
1740  ulint level;
1741  bool level_is_analyzed;
1742  ulint n_uniq;
1743  ulint n_prefix;
1744  ib_uint64_t* n_diff_on_level;
1745  ib_uint64_t total_recs;
1746  ib_uint64_t total_pages;
1747  boundaries_t* n_diff_boundaries;
1748  mtr_t mtr;
1749  ulint size;
1750  DBUG_ENTER("dict_stats_analyze_index");
1751 
1752  DBUG_PRINT("info", ("index: %s, online status: %d", index->name,
1754 
1755  DEBUG_PRINTF(" %s(index=%s)\n", __func__, index->name);
1756 
1757  dict_stats_empty_index(index);
1758 
1759  mtr_start(&mtr);
1760 
1761  mtr_s_lock(dict_index_get_lock(index), &mtr);
1762 
1763  size = btr_get_size(index, BTR_TOTAL_SIZE, &mtr);
1764 
1765  if (size != ULINT_UNDEFINED) {
1766  index->stat_index_size = size;
1767  size = btr_get_size(index, BTR_N_LEAF_PAGES, &mtr);
1768  }
1769 
1770  /* Release the X locks on the root page taken by btr_get_size() */
1771  mtr_commit(&mtr);
1772 
1773  switch (size) {
1774  case ULINT_UNDEFINED:
1775  dict_stats_assert_initialized_index(index);
1776  DBUG_VOID_RETURN;
1777  case 0:
1778  /* The root node of the tree is a leaf */
1779  size = 1;
1780  }
1781 
1782  index->stat_n_leaf_pages = size;
1783 
1784  mtr_start(&mtr);
1785 
1786  mtr_s_lock(dict_index_get_lock(index), &mtr);
1787 
1788  root_level = btr_height_get(index, &mtr);
1789 
1790  n_uniq = dict_index_get_n_unique(index);
1791 
1792  /* If the tree has just one level (and one page) or if the user
1793  has requested to sample too many pages then do full scan.
1794 
1795  For each n-column prefix (for n=1..n_uniq) N_SAMPLE_PAGES(index)
1796  will be sampled, so in total N_SAMPLE_PAGES(index) * n_uniq leaf
1797  pages will be sampled. If that number is bigger than the total
1798  number of leaf pages then do full scan of the leaf level instead
1799  since it will be faster and will give better results. */
1800 
1801  if (root_level == 0
1802  || N_SAMPLE_PAGES(index) * n_uniq > index->stat_n_leaf_pages) {
1803 
1804  if (root_level == 0) {
1805  DEBUG_PRINTF(" %s(): just one page, "
1806  "doing full scan\n", __func__);
1807  } else {
1808  DEBUG_PRINTF(" %s(): too many pages requested for "
1809  "sampling, doing full scan\n", __func__);
1810  }
1811 
1812  /* do full scan of level 0; save results directly
1813  into the index */
1814 
1815  dict_stats_analyze_index_level(index,
1816  0 /* leaf level */,
1817  index->stat_n_diff_key_vals,
1818  &total_recs,
1819  &total_pages,
1820  NULL /* boundaries not needed */,
1821  &mtr);
1822 
1823  for (ulint i = 0; i < n_uniq; i++) {
1824  index->stat_n_sample_sizes[i] = total_pages;
1825  }
1826 
1827  mtr_commit(&mtr);
1828 
1829  dict_stats_assert_initialized_index(index);
1830  DBUG_VOID_RETURN;
1831  }
1832 
1833  /* set to zero */
1834  n_diff_on_level = reinterpret_cast<ib_uint64_t*>
1835  (mem_zalloc(n_uniq * sizeof(ib_uint64_t)));
1836 
1837  n_diff_boundaries = new boundaries_t[n_uniq];
1838 
1839  /* total_recs is also used to estimate the number of pages on one
1840  level below, so at the start we have 1 page (the root) */
1841  total_recs = 1;
1842 
1843  /* Here we use the following optimization:
1844  If we find that level L is the first one (searching from the
1845  root) that contains at least D distinct keys when looking at
1846  the first n_prefix columns, then:
1847  if we look at the first n_prefix-1 columns then the first
1848  level that contains D distinct keys will be either L or a
1849  lower one.
1850  So if we find that the first level containing D distinct
1851  keys (on n_prefix columns) is L, we continue from L when
1852  searching for D distinct keys on n_prefix-1 columns. */
1853  level = root_level;
1854  level_is_analyzed = false;
1855 
1856  for (n_prefix = n_uniq; n_prefix >= 1; n_prefix--) {
1857 
1858  DEBUG_PRINTF(" %s(): searching level with >=%llu "
1859  "distinct records, n_prefix=%lu\n",
1860  __func__, N_DIFF_REQUIRED(index), n_prefix);
1861 
1862  /* Commit the mtr to release the tree S lock to allow
1863  other threads to do some work too. */
1864  mtr_commit(&mtr);
1865  mtr_start(&mtr);
1866  mtr_s_lock(dict_index_get_lock(index), &mtr);
1867  if (root_level != btr_height_get(index, &mtr)) {
1868  /* Just quit if the tree has changed beyond
1869  recognition here. The old stats from previous
1870  runs will remain in the values that we have
1871  not calculated yet. Initially when the index
1872  object is created the stats members are given
1873  some sensible values so leaving them untouched
1874  here even the first time will not cause us to
1875  read uninitialized memory later. */
1876  break;
1877  }
1878 
1879  /* check whether we should pick the current level;
1880  we pick level 1 even if it does not have enough
1881  distinct records because we do not want to scan the
1882  leaf level because it may contain too many records */
1883  if (level_is_analyzed
1884  && (n_diff_on_level[n_prefix - 1] >= N_DIFF_REQUIRED(index)
1885  || level == 1)) {
1886 
1887  goto found_level;
1888  }
1889 
1890  /* search for a level that contains enough distinct records */
1891 
1892  if (level_is_analyzed && level > 1) {
1893 
1894  /* if this does not hold we should be on
1895  "found_level" instead of here */
1896  ut_ad(n_diff_on_level[n_prefix - 1]
1897  < N_DIFF_REQUIRED(index));
1898 
1899  level--;
1900  level_is_analyzed = false;
1901  }
1902 
1903  /* descend into the tree, searching for "good enough" level */
1904  for (;;) {
1905 
1906  /* make sure we do not scan the leaf level
1907  accidentally, it may contain too many pages */
1908  ut_ad(level > 0);
1909 
1910  /* scanning the same level twice is an optimization
1911  bug */
1912  ut_ad(!level_is_analyzed);
1913 
1914  /* Do not scan if this would read too many pages.
1915  Here we use the following fact:
1916  the number of pages on level L equals the number
1917  of records on level L+1, thus we deduce that the
1918  following call would scan total_recs pages, because
1919  total_recs is left from the previous iteration when
1920  we scanned one level upper or we have not scanned any
1921  levels yet in which case total_recs is 1. */
1922  if (total_recs > N_SAMPLE_PAGES(index)) {
1923 
1924  /* if the above cond is true then we are
1925  not at the root level since on the root
1926  level total_recs == 1 (set before we
1927  enter the n-prefix loop) and cannot
1928  be > N_SAMPLE_PAGES(index) */
1929  ut_a(level != root_level);
1930 
1931  /* step one level back and be satisfied with
1932  whatever it contains */
1933  level++;
1934  level_is_analyzed = true;
1935 
1936  break;
1937  }
1938 
1939  dict_stats_analyze_index_level(index,
1940  level,
1941  n_diff_on_level,
1942  &total_recs,
1943  &total_pages,
1944  n_diff_boundaries,
1945  &mtr);
1946 
1947  level_is_analyzed = true;
1948 
1949  if (n_diff_on_level[n_prefix - 1]
1950  >= N_DIFF_REQUIRED(index)
1951  || level == 1) {
1952  /* we found a good level with many distinct
1953  records or we have reached the last level we
1954  could scan */
1955  break;
1956  }
1957 
1958  level--;
1959  level_is_analyzed = false;
1960  }
1961 found_level:
1962 
1963  DEBUG_PRINTF(" %s(): found level %lu that has " UINT64PF
1964  " distinct records for n_prefix=%lu\n",
1965  __func__, level, n_diff_on_level[n_prefix - 1],
1966  n_prefix);
1967 
1968  /* here we are either on level 1 or the level that we are on
1969  contains >= N_DIFF_REQUIRED distinct keys or we did not scan
1970  deeper levels because they would contain too many pages */
1971 
1972  ut_ad(level > 0);
1973 
1974  ut_ad(level_is_analyzed);
1975 
1976  /* pick some records from this level and dive below them for
1977  the given n_prefix */
1978 
1979  dict_stats_analyze_index_for_n_prefix(
1980  index, level, total_recs, n_prefix,
1981  n_diff_on_level[n_prefix - 1],
1982  &n_diff_boundaries[n_prefix - 1], &mtr);
1983  }
1984 
1985  mtr_commit(&mtr);
1986 
1987  delete[] n_diff_boundaries;
1988 
1989  mem_free(n_diff_on_level);
1990 
1991  dict_stats_assert_initialized_index(index);
1992  DBUG_VOID_RETURN;
1993 }
1994 
1995 /*********************************************************************/
2000 static
2001 dberr_t
2002 dict_stats_update_persistent(
2003 /*=========================*/
2004  dict_table_t* table)
2005 {
2007 
2008  DEBUG_PRINTF("%s(table=%s)\n", __func__, table->name);
2009 
2010  dict_table_stats_lock(table, RW_X_LATCH);
2011 
2012  /* analyze the clustered index first */
2013 
2014  index = dict_table_get_first_index(table);
2015 
2016  if (index == NULL
2017  || dict_index_is_corrupted(index)
2018  || (index->type | DICT_UNIQUE) != (DICT_CLUSTERED | DICT_UNIQUE)) {
2019 
2020  /* Table definition is corrupt */
2021  dict_table_stats_unlock(table, RW_X_LATCH);
2022  dict_stats_empty_table(table);
2023 
2024  return(DB_CORRUPTION);
2025  }
2026 
2027  ut_ad(!dict_index_is_univ(index));
2028 
2029  dict_stats_analyze_index(index);
2030 
2031  ulint n_unique = dict_index_get_n_unique(index);
2032 
2033  table->stat_n_rows = index->stat_n_diff_key_vals[n_unique - 1];
2034 
2036 
2037  /* analyze other indexes from the table, if any */
2038 
2039  table->stat_sum_of_other_index_sizes = 0;
2040 
2041  for (index = dict_table_get_next_index(index);
2042  index != NULL;
2043  index = dict_table_get_next_index(index)) {
2044 
2045  ut_ad(!dict_index_is_univ(index));
2046 
2047  if (index->type & DICT_FTS) {
2048  continue;
2049  }
2050 
2051  dict_stats_empty_index(index);
2052 
2053  if (dict_stats_should_ignore_index(index)) {
2054  continue;
2055  }
2056 
2057  if (!(table->stats_bg_flag & BG_STAT_SHOULD_QUIT)) {
2058  dict_stats_analyze_index(index);
2059  }
2060 
2062  += index->stat_index_size;
2063  }
2064 
2065  table->stats_last_recalc = ut_time();
2066 
2067  table->stat_modified_counter = 0;
2068 
2069  table->stat_initialized = TRUE;
2070 
2071  dict_stats_assert_initialized(table);
2072 
2073  dict_table_stats_unlock(table, RW_X_LATCH);
2074 
2075  return(DB_SUCCESS);
2076 }
2077 
2078 #include "mysql_com.h"
2079 /*********************************************************************/
2083 static
2084 dberr_t
2085 dict_stats_save_index_stat(
2086 /*=======================*/
2087  dict_index_t* index,
2088  lint last_update,
2089  const char* stat_name,
2090  ib_uint64_t stat_value,
2091  ib_uint64_t* sample_size,
2092  const char* stat_description)
2093 {
2094  pars_info_t* pinfo;
2095  dberr_t ret;
2096  char db_utf8[MAX_DB_UTF8_LEN];
2097  char table_utf8[MAX_TABLE_UTF8_LEN];
2098 
2099 #ifdef UNIV_SYNC_DEBUG
2100  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
2101 #endif /* UNIV_SYNC_DEBUG */
2102  ut_ad(mutex_own(&dict_sys->mutex));
2103 
2104  dict_fs2utf8(index->table->name, db_utf8, sizeof(db_utf8),
2105  table_utf8, sizeof(table_utf8));
2106 
2107  pinfo = pars_info_create();
2108  pars_info_add_str_literal(pinfo, "database_name", db_utf8);
2109  pars_info_add_str_literal(pinfo, "table_name", table_utf8);
2110  UNIV_MEM_ASSERT_RW_ABORT(index->name, strlen(index->name));
2111  pars_info_add_str_literal(pinfo, "index_name", index->name);
2112  UNIV_MEM_ASSERT_RW_ABORT(&last_update, 4);
2113  pars_info_add_int4_literal(pinfo, "last_update", last_update);
2114  UNIV_MEM_ASSERT_RW_ABORT(stat_name, strlen(stat_name));
2115  pars_info_add_str_literal(pinfo, "stat_name", stat_name);
2116  UNIV_MEM_ASSERT_RW_ABORT(&stat_value, 8);
2117  pars_info_add_ull_literal(pinfo, "stat_value", stat_value);
2118  if (sample_size != NULL) {
2119  UNIV_MEM_ASSERT_RW_ABORT(sample_size, 8);
2120  pars_info_add_ull_literal(pinfo, "sample_size", *sample_size);
2121  } else {
2122  pars_info_add_literal(pinfo, "sample_size", NULL,
2123  UNIV_SQL_NULL, DATA_FIXBINARY, 0);
2124  }
2125  UNIV_MEM_ASSERT_RW_ABORT(stat_description, strlen(stat_description));
2126  pars_info_add_str_literal(pinfo, "stat_description",
2127  stat_description);
2128 
2129  ret = dict_stats_exec_sql(
2130  pinfo,
2131  "PROCEDURE INDEX_STATS_SAVE_INSERT () IS\n"
2132  "BEGIN\n"
2133  "INSERT INTO \"" INDEX_STATS_NAME "\"\n"
2134  "VALUES\n"
2135  "(\n"
2136  ":database_name,\n"
2137  ":table_name,\n"
2138  ":index_name,\n"
2139  ":last_update,\n"
2140  ":stat_name,\n"
2141  ":stat_value,\n"
2142  ":sample_size,\n"
2143  ":stat_description\n"
2144  ");\n"
2145  "END;");
2146 
2147  if (ret == DB_DUPLICATE_KEY) {
2148 
2149  pinfo = pars_info_create();
2150  pars_info_add_str_literal(pinfo, "database_name", db_utf8);
2151  pars_info_add_str_literal(pinfo, "table_name", table_utf8);
2152  UNIV_MEM_ASSERT_RW_ABORT(index->name, strlen(index->name));
2153  pars_info_add_str_literal(pinfo, "index_name", index->name);
2154  UNIV_MEM_ASSERT_RW_ABORT(&last_update, 4);
2155  pars_info_add_int4_literal(pinfo, "last_update", last_update);
2156  UNIV_MEM_ASSERT_RW_ABORT(stat_name, strlen(stat_name));
2157  pars_info_add_str_literal(pinfo, "stat_name", stat_name);
2158  UNIV_MEM_ASSERT_RW_ABORT(&stat_value, 8);
2159  pars_info_add_ull_literal(pinfo, "stat_value", stat_value);
2160  if (sample_size != NULL) {
2161  UNIV_MEM_ASSERT_RW_ABORT(sample_size, 8);
2162  pars_info_add_ull_literal(pinfo, "sample_size", *sample_size);
2163  } else {
2164  pars_info_add_literal(pinfo, "sample_size", NULL,
2165  UNIV_SQL_NULL, DATA_FIXBINARY, 0);
2166  }
2167  UNIV_MEM_ASSERT_RW_ABORT(stat_description, strlen(stat_description));
2168  pars_info_add_str_literal(pinfo, "stat_description",
2169  stat_description);
2170 
2171  ret = dict_stats_exec_sql(
2172  pinfo,
2173  "PROCEDURE INDEX_STATS_SAVE_UPDATE () IS\n"
2174  "BEGIN\n"
2175  "UPDATE \"" INDEX_STATS_NAME "\" SET\n"
2176  "last_update = :last_update,\n"
2177  "stat_value = :stat_value,\n"
2178  "sample_size = :sample_size,\n"
2179  "stat_description = :stat_description\n"
2180  "WHERE\n"
2181  "database_name = :database_name AND\n"
2182  "table_name = :table_name AND\n"
2183  "index_name = :index_name AND\n"
2184  "stat_name = :stat_name;\n"
2185  "END;");
2186  }
2187 
2188  if (ret != DB_SUCCESS) {
2189  char buf_table[MAX_FULL_NAME_LEN];
2190  char buf_index[MAX_FULL_NAME_LEN];
2191  ut_print_timestamp(stderr);
2192  fprintf(stderr,
2193  " InnoDB: Cannot save index statistics for table "
2194  "%s, index %s, stat name \"%s\": %s\n",
2195  ut_format_name(index->table->name, TRUE,
2196  buf_table, sizeof(buf_table)),
2197  ut_format_name(index->name, FALSE,
2198  buf_index, sizeof(buf_index)),
2199  stat_name, ut_strerr(ret));
2200  }
2201 
2202  return(ret);
2203 }
2204 
2205 /*********************************************************************/
2208 static
2209 dberr_t
2210 dict_stats_save(
2211 /*============*/
2212  dict_table_t* table_orig)
2213 {
2214  pars_info_t* pinfo;
2215  lint now;
2216  dberr_t ret;
2218  char db_utf8[MAX_DB_UTF8_LEN];
2219  char table_utf8[MAX_TABLE_UTF8_LEN];
2220 
2221  table = dict_stats_snapshot_create(table_orig);
2222 
2223  dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
2224  table_utf8, sizeof(table_utf8));
2225 
2226  rw_lock_x_lock(&dict_operation_lock);
2227  mutex_enter(&dict_sys->mutex);
2228 
2229  /* MySQL's timestamp is 4 byte, so we use
2230  pars_info_add_int4_literal() which takes a lint arg, so "now" is
2231  lint */
2232  now = (lint) ut_time();
2233 
2234 #define PREPARE_PINFO_FOR_TABLE_SAVE(p, t, n) \
2235  do { \
2236  pars_info_add_str_literal((p), "database_name", db_utf8); \
2237  pars_info_add_str_literal((p), "table_name", table_utf8); \
2238  pars_info_add_int4_literal((p), "last_update", (n)); \
2239  pars_info_add_ull_literal((p), "n_rows", (t)->stat_n_rows); \
2240  pars_info_add_ull_literal((p), "clustered_index_size", \
2241  (t)->stat_clustered_index_size); \
2242  pars_info_add_ull_literal((p), "sum_of_other_index_sizes", \
2243  (t)->stat_sum_of_other_index_sizes); \
2244  } while(false);
2245 
2246  pinfo = pars_info_create();
2247 
2248  PREPARE_PINFO_FOR_TABLE_SAVE(pinfo, table, now);
2249 
2250  ret = dict_stats_exec_sql(
2251  pinfo,
2252  "PROCEDURE TABLE_STATS_SAVE_INSERT () IS\n"
2253  "BEGIN\n"
2254  "INSERT INTO \"" TABLE_STATS_NAME "\"\n"
2255  "VALUES\n"
2256  "(\n"
2257  ":database_name,\n"
2258  ":table_name,\n"
2259  ":last_update,\n"
2260  ":n_rows,\n"
2261  ":clustered_index_size,\n"
2262  ":sum_of_other_index_sizes\n"
2263  ");\n"
2264  "END;");
2265 
2266  if (ret == DB_DUPLICATE_KEY) {
2267  pinfo = pars_info_create();
2268 
2269  PREPARE_PINFO_FOR_TABLE_SAVE(pinfo, table, now);
2270 
2271  ret = dict_stats_exec_sql(
2272  pinfo,
2273  "PROCEDURE TABLE_STATS_SAVE_UPDATE () IS\n"
2274  "BEGIN\n"
2275  "UPDATE \"" TABLE_STATS_NAME "\" SET\n"
2276  "last_update = :last_update,\n"
2277  "n_rows = :n_rows,\n"
2278  "clustered_index_size = :clustered_index_size,\n"
2279  "sum_of_other_index_sizes = "
2280  " :sum_of_other_index_sizes\n"
2281  "WHERE\n"
2282  "database_name = :database_name AND\n"
2283  "table_name = :table_name;\n"
2284  "END;");
2285  }
2286 
2287  if (ret != DB_SUCCESS) {
2288  char buf[MAX_FULL_NAME_LEN];
2289  ut_print_timestamp(stderr);
2290  fprintf(stderr,
2291  " InnoDB: Cannot save table statistics for table "
2292  "%s: %s\n",
2293  ut_format_name(table->name, TRUE, buf, sizeof(buf)),
2294  ut_strerr(ret));
2295  goto end;
2296  }
2297 
2299 
2300  for (index = dict_table_get_first_index(table);
2301  index != NULL;
2302  index = dict_table_get_next_index(index)) {
2303 
2304  if (dict_stats_should_ignore_index(index)) {
2305  continue;
2306  }
2307 
2308  ut_ad(!dict_index_is_univ(index));
2309 
2310  ret = dict_stats_save_index_stat(index, now, "size",
2311  index->stat_index_size,
2312  NULL,
2313  "Number of pages "
2314  "in the index");
2315  if (ret != DB_SUCCESS) {
2316  goto end;
2317  }
2318 
2319  ret = dict_stats_save_index_stat(index, now, "n_leaf_pages",
2320  index->stat_n_leaf_pages,
2321  NULL,
2322  "Number of leaf pages "
2323  "in the index");
2324  if (ret != DB_SUCCESS) {
2325  goto end;
2326  }
2327 
2328  for (ulint i = 0; i < index->n_uniq; i++) {
2329 
2330  char stat_name[16];
2331  char stat_description[1024];
2332  ulint j;
2333 
2334  ut_snprintf(stat_name, sizeof(stat_name),
2335  "n_diff_pfx%02lu", i + 1);
2336 
2337  /* craft a string that contains the columns names */
2338  ut_snprintf(stat_description,
2339  sizeof(stat_description),
2340  "%s", index->fields[0].name);
2341  for (j = 1; j <= i; j++) {
2342  size_t len;
2343 
2344  len = strlen(stat_description);
2345 
2346  ut_snprintf(stat_description + len,
2347  sizeof(stat_description) - len,
2348  ",%s", index->fields[j].name);
2349  }
2350 
2351  ret = dict_stats_save_index_stat(
2352  index, now, stat_name,
2353  index->stat_n_diff_key_vals[i],
2354  &index->stat_n_sample_sizes[i],
2355  stat_description);
2356 
2357  if (ret != DB_SUCCESS) {
2358  goto end;
2359  }
2360  }
2361  }
2362 
2363 end:
2364  mutex_exit(&dict_sys->mutex);
2365  rw_lock_x_unlock(&dict_operation_lock);
2366 
2367  dict_stats_snapshot_free(table);
2368 
2369  return(ret);
2370 }
2371 
2372 /*********************************************************************/
2378 static
2379 ibool
2380 dict_stats_fetch_table_stats_step(
2381 /*==============================*/
2382  void* node_void,
2383  void* table_void)
2384 {
2385  sel_node_t* node = (sel_node_t*) node_void;
2386  dict_table_t* table = (dict_table_t*) table_void;
2387  que_common_t* cnode;
2388  int i;
2389 
2390  /* this should loop exactly 3 times - for
2391  n_rows,clustered_index_size,sum_of_other_index_sizes */
2392  for (cnode = static_cast<que_common_t*>(node->select_list), i = 0;
2393  cnode != NULL;
2394  cnode = static_cast<que_common_t*>(que_node_get_next(cnode)),
2395  i++) {
2396 
2397  const byte* data;
2398  dfield_t* dfield = que_node_get_val(cnode);
2399  dtype_t* type = dfield_get_type(dfield);
2400  ulint len = dfield_get_len(dfield);
2401 
2402  data = static_cast<const byte*>(dfield_get_data(dfield));
2403 
2404  switch (i) {
2405  case 0: /* mysql.innodb_table_stats.n_rows */
2406 
2407  ut_a(dtype_get_mtype(type) == DATA_INT);
2408  ut_a(len == 8);
2409 
2410  table->stat_n_rows = mach_read_from_8(data);
2411 
2412  break;
2413 
2414  case 1: /* mysql.innodb_table_stats.clustered_index_size */
2415 
2416  ut_a(dtype_get_mtype(type) == DATA_INT);
2417  ut_a(len == 8);
2418 
2420  = (ulint) mach_read_from_8(data);
2421 
2422  break;
2423 
2424  case 2: /* mysql.innodb_table_stats.sum_of_other_index_sizes */
2425 
2426  ut_a(dtype_get_mtype(type) == DATA_INT);
2427  ut_a(len == 8);
2428 
2430  = (ulint) mach_read_from_8(data);
2431 
2432  break;
2433 
2434  default:
2435 
2436  /* someone changed SELECT
2437  n_rows,clustered_index_size,sum_of_other_index_sizes
2438  to select more columns from innodb_table_stats without
2439  adjusting here */
2440  ut_error;
2441  }
2442  }
2443 
2444  /* if i < 3 this means someone changed the
2445  SELECT n_rows,clustered_index_size,sum_of_other_index_sizes
2446  to select less columns from innodb_table_stats without adjusting here;
2447  if i > 3 we would have ut_error'ed earlier */
2448  ut_a(i == 3 /*n_rows,clustered_index_size,sum_of_other_index_sizes*/);
2449 
2450  /* XXX this is not used but returning non-NULL is necessary */
2451  return(TRUE);
2452 }
2453 
2456 struct index_fetch_t {
2457  dict_table_t* table;
2458  bool stats_were_modified;
2460 };
2461 
2462 /*********************************************************************/
2479 static
2480 ibool
2481 dict_stats_fetch_index_stats_step(
2482 /*==============================*/
2483  void* node_void,
2484  void* arg_void)
2486 {
2487  sel_node_t* node = (sel_node_t*) node_void;
2488  index_fetch_t* arg = (index_fetch_t*) arg_void;
2489  dict_table_t* table = arg->table;
2490  dict_index_t* index = NULL;
2491  que_common_t* cnode;
2492  const char* stat_name = NULL;
2493  ulint stat_name_len = ULINT_UNDEFINED;
2494  ib_uint64_t stat_value = UINT64_UNDEFINED;
2495  ib_uint64_t sample_size = UINT64_UNDEFINED;
2496  int i;
2497 
2498  /* this should loop exactly 4 times - for the columns that
2499  were selected: index_name,stat_name,stat_value,sample_size */
2500  for (cnode = static_cast<que_common_t*>(node->select_list), i = 0;
2501  cnode != NULL;
2502  cnode = static_cast<que_common_t*>(que_node_get_next(cnode)),
2503  i++) {
2504 
2505  const byte* data;
2506  dfield_t* dfield = que_node_get_val(cnode);
2507  dtype_t* type = dfield_get_type(dfield);
2508  ulint len = dfield_get_len(dfield);
2509 
2510  data = static_cast<const byte*>(dfield_get_data(dfield));
2511 
2512  switch (i) {
2513  case 0: /* mysql.innodb_index_stats.index_name */
2514 
2515  ut_a(dtype_get_mtype(type) == DATA_VARMYSQL);
2516 
2517  /* search for index in table's indexes whose name
2518  matches data; the fetched index name is in data,
2519  has no terminating '\0' and has length len */
2520  for (index = dict_table_get_first_index(table);
2521  index != NULL;
2522  index = dict_table_get_next_index(index)) {
2523 
2524  if (strlen(index->name) == len
2525  && memcmp(index->name, data, len) == 0) {
2526  /* the corresponding index was found */
2527  break;
2528  }
2529  }
2530 
2531  /* if index is NULL here this means that
2532  mysql.innodb_index_stats contains more rows than the
2533  number of indexes in the table; this is ok, we just
2534  return ignoring those extra rows; in other words
2535  dict_stats_fetch_index_stats_step() has been called
2536  for a row from index_stats with unknown index_name
2537  column */
2538  if (index == NULL) {
2539 
2540  return(TRUE);
2541  }
2542 
2543  break;
2544 
2545  case 1: /* mysql.innodb_index_stats.stat_name */
2546 
2547  ut_a(dtype_get_mtype(type) == DATA_VARMYSQL);
2548 
2549  ut_a(index != NULL);
2550 
2551  stat_name = (const char*) data;
2552  stat_name_len = len;
2553 
2554  break;
2555 
2556  case 2: /* mysql.innodb_index_stats.stat_value */
2557 
2558  ut_a(dtype_get_mtype(type) == DATA_INT);
2559  ut_a(len == 8);
2560 
2561  ut_a(index != NULL);
2562  ut_a(stat_name != NULL);
2563  ut_a(stat_name_len != ULINT_UNDEFINED);
2564 
2565  stat_value = mach_read_from_8(data);
2566 
2567  break;
2568 
2569  case 3: /* mysql.innodb_index_stats.sample_size */
2570 
2571  ut_a(dtype_get_mtype(type) == DATA_INT);
2572  ut_a(len == 8 || len == UNIV_SQL_NULL);
2573 
2574  ut_a(index != NULL);
2575  ut_a(stat_name != NULL);
2576  ut_a(stat_name_len != ULINT_UNDEFINED);
2577  ut_a(stat_value != UINT64_UNDEFINED);
2578 
2579  if (len == UNIV_SQL_NULL) {
2580  break;
2581  }
2582  /* else */
2583 
2584  sample_size = mach_read_from_8(data);
2585 
2586  break;
2587 
2588  default:
2589 
2590  /* someone changed
2591  SELECT index_name,stat_name,stat_value,sample_size
2592  to select more columns from innodb_index_stats without
2593  adjusting here */
2594  ut_error;
2595  }
2596  }
2597 
2598  /* if i < 4 this means someone changed the
2599  SELECT index_name,stat_name,stat_value,sample_size
2600  to select less columns from innodb_index_stats without adjusting here;
2601  if i > 4 we would have ut_error'ed earlier */
2602  ut_a(i == 4 /* index_name,stat_name,stat_value,sample_size */);
2603 
2604  ut_a(index != NULL);
2605  ut_a(stat_name != NULL);
2606  ut_a(stat_name_len != ULINT_UNDEFINED);
2607  ut_a(stat_value != UINT64_UNDEFINED);
2608  /* sample_size could be UINT64_UNDEFINED here, if it is NULL */
2609 
2610 #define PFX "n_diff_pfx"
2611 #define PFX_LEN 10
2612 
2613  if (stat_name_len == 4 /* strlen("size") */
2614  && strncasecmp("size", stat_name, stat_name_len) == 0) {
2615  index->stat_index_size = (ulint) stat_value;
2616  arg->stats_were_modified = true;
2617  } else if (stat_name_len == 12 /* strlen("n_leaf_pages") */
2618  && strncasecmp("n_leaf_pages", stat_name, stat_name_len)
2619  == 0) {
2620  index->stat_n_leaf_pages = (ulint) stat_value;
2621  arg->stats_were_modified = true;
2622  } else if (stat_name_len > PFX_LEN /* e.g. stat_name=="n_diff_pfx01" */
2623  && strncasecmp(PFX, stat_name, PFX_LEN) == 0) {
2624 
2625  const char* num_ptr;
2626  unsigned long n_pfx;
2627 
2628  /* point num_ptr into "1" from "n_diff_pfx12..." */
2629  num_ptr = stat_name + PFX_LEN;
2630 
2631  /* stat_name should have exactly 2 chars appended to PFX
2632  and they should be digits */
2633  if (stat_name_len != PFX_LEN + 2
2634  || num_ptr[0] < '0' || num_ptr[0] > '9'
2635  || num_ptr[1] < '0' || num_ptr[1] > '9') {
2636 
2637  char db_utf8[MAX_DB_UTF8_LEN];
2638  char table_utf8[MAX_TABLE_UTF8_LEN];
2639 
2640  dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
2641  table_utf8, sizeof(table_utf8));
2642 
2643  ut_print_timestamp(stderr);
2644  fprintf(stderr,
2645  " InnoDB: Ignoring strange row from "
2646  "%s WHERE "
2647  "database_name = '%s' AND "
2648  "table_name = '%s' AND "
2649  "index_name = '%s' AND "
2650  "stat_name = '%.*s'; because stat_name "
2651  "is malformed\n",
2652  INDEX_STATS_NAME_PRINT,
2653  db_utf8,
2654  table_utf8,
2655  index->name,
2656  (int) stat_name_len,
2657  stat_name);
2658  return(TRUE);
2659  }
2660  /* else */
2661 
2662  /* extract 12 from "n_diff_pfx12..." into n_pfx
2663  note that stat_name does not have a terminating '\0' */
2664  n_pfx = (num_ptr[0] - '0') * 10 + (num_ptr[1] - '0');
2665 
2666  ulint n_uniq = index->n_uniq;
2667 
2668  if (n_pfx == 0 || n_pfx > n_uniq) {
2669 
2670  char db_utf8[MAX_DB_UTF8_LEN];
2671  char table_utf8[MAX_TABLE_UTF8_LEN];
2672 
2673  dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
2674  table_utf8, sizeof(table_utf8));
2675 
2676  ut_print_timestamp(stderr);
2677  fprintf(stderr,
2678  " InnoDB: Ignoring strange row from "
2679  "%s WHERE "
2680  "database_name = '%s' AND "
2681  "table_name = '%s' AND "
2682  "index_name = '%s' AND "
2683  "stat_name = '%.*s'; because stat_name is "
2684  "out of range, the index has %lu unique "
2685  "columns\n",
2686  INDEX_STATS_NAME_PRINT,
2687  db_utf8,
2688  table_utf8,
2689  index->name,
2690  (int) stat_name_len,
2691  stat_name,
2692  n_uniq);
2693  return(TRUE);
2694  }
2695  /* else */
2696 
2697  index->stat_n_diff_key_vals[n_pfx - 1] = stat_value;
2698 
2699  if (sample_size != UINT64_UNDEFINED) {
2700  index->stat_n_sample_sizes[n_pfx - 1] = sample_size;
2701  } else {
2702  /* hmm, strange... the user must have UPDATEd the
2703  table manually and SET sample_size = NULL */
2704  index->stat_n_sample_sizes[n_pfx - 1] = 0;
2705  }
2706 
2707  index->stat_n_non_null_key_vals[n_pfx - 1] = 0;
2708 
2709  arg->stats_were_modified = true;
2710  } else {
2711  /* silently ignore rows with unknown stat_name, the
2712  user may have developed her own stats */
2713  }
2714 
2715  /* XXX this is not used but returning non-NULL is necessary */
2716  return(TRUE);
2717 }
2718 
2719 /*********************************************************************/
2722 static
2723 dberr_t
2724 dict_stats_fetch_from_ps(
2725 /*=====================*/
2726  dict_table_t* table)
2727 {
2728  index_fetch_t index_fetch_arg;
2729  trx_t* trx;
2730  pars_info_t* pinfo;
2731  dberr_t ret;
2732  char db_utf8[MAX_DB_UTF8_LEN];
2733  char table_utf8[MAX_TABLE_UTF8_LEN];
2734 
2735  ut_ad(!mutex_own(&dict_sys->mutex));
2736 
2737  /* Initialize all stats to dummy values before fetching because if
2738  the persistent storage contains incomplete stats (e.g. missing stats
2739  for some index) then we would end up with (partially) uninitialized
2740  stats. */
2741  dict_stats_empty_table(table);
2742 
2744 
2745  /* Use 'read-uncommitted' so that the SELECTs we execute
2746  do not get blocked in case some user has locked the rows we
2747  are SELECTing */
2748 
2749  trx->isolation_level = TRX_ISO_READ_UNCOMMITTED;
2750 
2751  trx_start_if_not_started(trx);
2752 
2753  dict_fs2utf8(table->name, db_utf8, sizeof(db_utf8),
2754  table_utf8, sizeof(table_utf8));
2755 
2756  pinfo = pars_info_create();
2757 
2758  pars_info_add_str_literal(pinfo, "database_name", db_utf8);
2759 
2760  pars_info_add_str_literal(pinfo, "table_name", table_utf8);
2761 
2763  "fetch_table_stats_step",
2764  dict_stats_fetch_table_stats_step,
2765  table);
2766 
2767  index_fetch_arg.table = table;
2768  index_fetch_arg.stats_were_modified = false;
2770  "fetch_index_stats_step",
2771  dict_stats_fetch_index_stats_step,
2772  &index_fetch_arg);
2773 
2774  ret = que_eval_sql(pinfo,
2775  "PROCEDURE FETCH_STATS () IS\n"
2776  "found INT;\n"
2777  "DECLARE FUNCTION fetch_table_stats_step;\n"
2778  "DECLARE FUNCTION fetch_index_stats_step;\n"
2779  "DECLARE CURSOR table_stats_cur IS\n"
2780  " SELECT\n"
2781  /* if you change the selected fields, be
2782  sure to adjust
2783  dict_stats_fetch_table_stats_step() */
2784  " n_rows,\n"
2785  " clustered_index_size,\n"
2786  " sum_of_other_index_sizes\n"
2787  " FROM \"" TABLE_STATS_NAME "\"\n"
2788  " WHERE\n"
2789  " database_name = :database_name AND\n"
2790  " table_name = :table_name;\n"
2791  "DECLARE CURSOR index_stats_cur IS\n"
2792  " SELECT\n"
2793  /* if you change the selected fields, be
2794  sure to adjust
2795  dict_stats_fetch_index_stats_step() */
2796  " index_name,\n"
2797  " stat_name,\n"
2798  " stat_value,\n"
2799  " sample_size\n"
2800  " FROM \"" INDEX_STATS_NAME "\"\n"
2801  " WHERE\n"
2802  " database_name = :database_name AND\n"
2803  " table_name = :table_name;\n"
2804 
2805  "BEGIN\n"
2806 
2807  "OPEN table_stats_cur;\n"
2808  "FETCH table_stats_cur INTO\n"
2809  " fetch_table_stats_step();\n"
2810  "IF (SQL % NOTFOUND) THEN\n"
2811  " CLOSE table_stats_cur;\n"
2812  " RETURN;\n"
2813  "END IF;\n"
2814  "CLOSE table_stats_cur;\n"
2815 
2816  "OPEN index_stats_cur;\n"
2817  "found := 1;\n"
2818  "WHILE found = 1 LOOP\n"
2819  " FETCH index_stats_cur INTO\n"
2820  " fetch_index_stats_step();\n"
2821  " IF (SQL % NOTFOUND) THEN\n"
2822  " found := 0;\n"
2823  " END IF;\n"
2824  "END LOOP;\n"
2825  "CLOSE index_stats_cur;\n"
2826 
2827  "END;",
2828  TRUE, trx);
2829  /* pinfo is freed by que_eval_sql() */
2830 
2831  trx_commit_for_mysql(trx);
2832 
2834 
2835  if (!index_fetch_arg.stats_were_modified) {
2836  return(DB_STATS_DO_NOT_EXIST);
2837  }
2838 
2839  return(ret);
2840 }
2841 
2842 /*********************************************************************/
2844 UNIV_INTERN
2845 void
2847 /*========================*/
2848  dict_index_t* index)
2849 {
2850  DBUG_ENTER("dict_stats_update_for_index");
2851 
2852  ut_ad(!mutex_own(&dict_sys->mutex));
2853 
2855 
2856  if (dict_stats_persistent_storage_check(false)) {
2857  dict_table_stats_lock(index->table, RW_X_LATCH);
2858  dict_stats_analyze_index(index);
2859  dict_table_stats_unlock(index->table, RW_X_LATCH);
2860  dict_stats_save(index->table);
2861  DBUG_VOID_RETURN;
2862  }
2863  /* else */
2864 
2865  /* Fall back to transient stats since the persistent
2866  storage is not present or is corrupted */
2867  char buf_table[MAX_FULL_NAME_LEN];
2868  char buf_index[MAX_FULL_NAME_LEN];
2869  ut_print_timestamp(stderr);
2870  fprintf(stderr,
2871  " InnoDB: Recalculation of persistent statistics "
2872  "requested for table %s index %s but the required "
2873  "persistent statistics storage is not present or is "
2874  "corrupted. Using transient stats instead.\n",
2875  ut_format_name(index->table->name, TRUE,
2876  buf_table, sizeof(buf_table)),
2877  ut_format_name(index->name, FALSE,
2878  buf_index, sizeof(buf_index)));
2879  }
2880 
2881  dict_table_stats_lock(index->table, RW_X_LATCH);
2882  dict_stats_update_transient_for_index(index);
2883  dict_table_stats_unlock(index->table, RW_X_LATCH);
2884 
2885  DBUG_VOID_RETURN;
2886 }
2887 
2888 /*********************************************************************/
2892 UNIV_INTERN
2893 dberr_t
2895 /*==============*/
2896  dict_table_t* table,
2897  dict_stats_upd_option_t stats_upd_option)
2902 {
2903  char buf[MAX_FULL_NAME_LEN];
2904 
2905  ut_ad(!mutex_own(&dict_sys->mutex));
2906 
2907  if (table->ibd_file_missing) {
2908  ut_print_timestamp(stderr);
2909  fprintf(stderr,
2910  " InnoDB: cannot calculate statistics for table %s "
2911  "because the .ibd file is missing. For help, please "
2912  "refer to " REFMAN "innodb-troubleshooting.html\n",
2913  ut_format_name(table->name, TRUE, buf, sizeof(buf)));
2914  dict_stats_empty_table(table);
2915  return(DB_TABLESPACE_DELETED);
2917  /* If we have set a high innodb_force_recovery level, do
2918  not calculate statistics, as a badly corrupted index can
2919  cause a crash in it. */
2920  dict_stats_empty_table(table);
2921  return(DB_SUCCESS);
2922  }
2923 
2924  switch (stats_upd_option) {
2925  case DICT_STATS_RECALC_PERSISTENT:
2926 
2927  if (srv_read_only_mode) {
2928  goto transient;
2929  }
2930 
2931  /* Persistent recalculation requested, called from
2932  1) ANALYZE TABLE, or
2933  2) the auto recalculation background thread, or
2934  3) open table if stats do not exist on disk and auto recalc
2935  is enabled */
2936 
2937  /* InnoDB internal tables (e.g. SYS_TABLES) cannot have
2938  persistent stats enabled */
2939  ut_a(strchr(table->name, '/') != NULL);
2940 
2941  /* check if the persistent statistics storage exists
2942  before calling the potentially slow function
2943  dict_stats_update_persistent(); that is a
2944  prerequisite for dict_stats_save() succeeding */
2945  if (dict_stats_persistent_storage_check(false)) {
2946 
2947  dberr_t err;
2948 
2949  err = dict_stats_update_persistent(table);
2950 
2951  if (err != DB_SUCCESS) {
2952  return(err);
2953  }
2954 
2955  err = dict_stats_save(table);
2956 
2957  return(err);
2958  }
2959 
2960  /* Fall back to transient stats since the persistent
2961  storage is not present or is corrupted */
2962 
2963  ut_print_timestamp(stderr);
2964  fprintf(stderr,
2965  " InnoDB: Recalculation of persistent statistics "
2966  "requested for table %s but the required persistent "
2967  "statistics storage is not present or is corrupted. "
2968  "Using transient stats instead.\n",
2969  ut_format_name(table->name, TRUE, buf, sizeof(buf)));
2970 
2971  goto transient;
2972 
2973  case DICT_STATS_RECALC_TRANSIENT:
2974 
2975  goto transient;
2976 
2977  case DICT_STATS_EMPTY_TABLE:
2978 
2979  dict_stats_empty_table(table);
2980 
2981  /* If table is using persistent stats,
2982  then save the stats on disk */
2983 
2984  if (dict_stats_is_persistent_enabled(table)) {
2985 
2986  if (dict_stats_persistent_storage_check(false)) {
2987 
2988  return(dict_stats_save(table));
2989  }
2990 
2991  return(DB_STATS_DO_NOT_EXIST);
2992  }
2993 
2994  return(DB_SUCCESS);
2995 
2996  case DICT_STATS_FETCH_ONLY_IF_NOT_IN_MEMORY:
2997 
2998  /* fetch requested, either fetch from persistent statistics
2999  storage or use the old method */
3000 
3001  if (table->stat_initialized) {
3002  return(DB_SUCCESS);
3003  }
3004 
3005  /* InnoDB internal tables (e.g. SYS_TABLES) cannot have
3006  persistent stats enabled */
3007  ut_a(strchr(table->name, '/') != NULL);
3008 
3009  if (!dict_stats_persistent_storage_check(false)) {
3010  /* persistent statistics storage does not exist
3011  or is corrupted, calculate the transient stats */
3012 
3013  ut_print_timestamp(stderr);
3014  fprintf(stderr,
3015  " InnoDB: Error: Fetch of persistent "
3016  "statistics requested for table %s but the "
3017  "required system tables %s and %s are not "
3018  "present or have unexpected structure. "
3019  "Using transient stats instead.\n",
3020  ut_format_name(table->name, TRUE,
3021  buf, sizeof(buf)),
3022  TABLE_STATS_NAME_PRINT,
3023  INDEX_STATS_NAME_PRINT);
3024 
3025  goto transient;
3026  }
3027 
3028  dict_table_t* t;
3029 
3030  /* Create a dummy table object with the same name and
3031  indexes, suitable for fetching the stats into it. */
3032  t = dict_stats_table_clone_create(table);
3033 
3034  dberr_t err = dict_stats_fetch_from_ps(t);
3035 
3037  t->stat_modified_counter = 0;
3038 
3039  switch (err) {
3040  case DB_SUCCESS:
3041 
3042  dict_table_stats_lock(table, RW_X_LATCH);
3043 
3044  /* Initialize all stats to dummy values before
3045  copying because dict_stats_table_clone_create() does
3046  skip corrupted indexes so our dummy object 't' may
3047  have less indexes than the real object 'table'. */
3048  dict_stats_empty_table(table);
3049 
3050  dict_stats_copy(table, t);
3051 
3052  dict_stats_assert_initialized(table);
3053 
3054  dict_table_stats_unlock(table, RW_X_LATCH);
3055 
3056  dict_stats_table_clone_free(t);
3057 
3058  return(DB_SUCCESS);
3059  case DB_STATS_DO_NOT_EXIST:
3060 
3061  dict_stats_table_clone_free(t);
3062 
3063  if (srv_read_only_mode) {
3064  goto transient;
3065  }
3066 
3067  if (dict_stats_auto_recalc_is_enabled(table)) {
3068  return(dict_stats_update(
3069  table,
3070  DICT_STATS_RECALC_PERSISTENT));
3071  }
3072 
3073  ut_format_name(table->name, TRUE, buf, sizeof(buf));
3074  ut_print_timestamp(stderr);
3075  fprintf(stderr,
3076  " InnoDB: Trying to use table %s which has "
3077  "persistent statistics enabled, but auto "
3078  "recalculation turned off and the statistics "
3079  "do not exist in %s and %s. Please either run "
3080  "\"ANALYZE TABLE %s;\" manually or enable the "
3081  "auto recalculation with "
3082  "\"ALTER TABLE %s STATS_AUTO_RECALC=1;\". "
3083  "InnoDB will now use transient statistics for "
3084  "%s.\n",
3085  buf, TABLE_STATS_NAME, INDEX_STATS_NAME, buf,
3086  buf, buf);
3087 
3088  goto transient;
3089  default:
3090 
3091  dict_stats_table_clone_free(t);
3092 
3093  ut_print_timestamp(stderr);
3094  fprintf(stderr,
3095  " InnoDB: Error fetching persistent statistics "
3096  "for table %s from %s and %s: %s. "
3097  "Using transient stats method instead.\n",
3098  ut_format_name(table->name, TRUE, buf,
3099  sizeof(buf)),
3100  TABLE_STATS_NAME,
3101  INDEX_STATS_NAME,
3102  ut_strerr(err));
3103 
3104  goto transient;
3105  }
3106  /* no "default:" in order to produce a compilation warning
3107  about unhandled enumeration value */
3108  }
3109 
3110 transient:
3111 
3112  dict_table_stats_lock(table, RW_X_LATCH);
3113 
3115 
3116  dict_table_stats_unlock(table, RW_X_LATCH);
3117 
3118  return(DB_SUCCESS);
3119 }
3120 
3121 /*********************************************************************/
3132 UNIV_INTERN
3133 dberr_t
3135 /*==================*/
3136  const char* db_and_table,
3137  const char* iname,
3138  char* errstr,
3140  ulint errstr_sz)
3141 {
3142  char db_utf8[MAX_DB_UTF8_LEN];
3143  char table_utf8[MAX_TABLE_UTF8_LEN];
3144  pars_info_t* pinfo;
3145  dberr_t ret;
3146 
3147  ut_ad(!mutex_own(&dict_sys->mutex));
3148 
3149  /* skip indexes whose table names do not contain a database name
3150  e.g. if we are dropping an index from SYS_TABLES */
3151  if (strchr(db_and_table, '/') == NULL) {
3152 
3153  return(DB_SUCCESS);
3154  }
3155 
3156  dict_fs2utf8(db_and_table, db_utf8, sizeof(db_utf8),
3157  table_utf8, sizeof(table_utf8));
3158 
3159  pinfo = pars_info_create();
3160 
3161  pars_info_add_str_literal(pinfo, "database_name", db_utf8);
3162 
3163  pars_info_add_str_literal(pinfo, "table_name", table_utf8);
3164 
3165  pars_info_add_str_literal(pinfo, "index_name", iname);
3166 
3167  rw_lock_x_lock(&dict_operation_lock);
3168  mutex_enter(&dict_sys->mutex);
3169 
3170  ret = dict_stats_exec_sql(
3171  pinfo,
3172  "PROCEDURE DROP_INDEX_STATS () IS\n"
3173  "BEGIN\n"
3174  "DELETE FROM \"" INDEX_STATS_NAME "\" WHERE\n"
3175  "database_name = :database_name AND\n"
3176  "table_name = :table_name AND\n"
3177  "index_name = :index_name;\n"
3178  "END;\n");
3179 
3180  mutex_exit(&dict_sys->mutex);
3181  rw_lock_x_unlock(&dict_operation_lock);
3182 
3183  if (ret == DB_STATS_DO_NOT_EXIST) {
3184  ret = DB_SUCCESS;
3185  }
3186 
3187  if (ret != DB_SUCCESS) {
3188  ut_snprintf(errstr, errstr_sz,
3189  "Unable to delete statistics for index %s "
3190  "from %s%s: %s. They can be deleted later using "
3191  "DELETE FROM %s WHERE "
3192  "database_name = '%s' AND "
3193  "table_name = '%s' AND "
3194  "index_name = '%s';",
3195  iname,
3196  INDEX_STATS_NAME_PRINT,
3197  (ret == DB_LOCK_WAIT_TIMEOUT
3198  ? " because the rows are locked"
3199  : ""),
3200  ut_strerr(ret),
3201  INDEX_STATS_NAME_PRINT,
3202  db_utf8,
3203  table_utf8,
3204  iname);
3205 
3206  ut_print_timestamp(stderr);
3207  fprintf(stderr, " InnoDB: %s\n", errstr);
3208  }
3209 
3210  return(ret);
3211 }
3212 
3213 /*********************************************************************/
3219 UNIV_INLINE
3220 dberr_t
3222 /*===============================*/
3223  const char* database_name,
3224  const char* table_name)
3225 {
3226  pars_info_t* pinfo;
3227  dberr_t ret;
3228 
3229 #ifdef UNIV_SYNC_DEBUG
3230  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
3231 #endif /* UNIV_SYNC_DEBUG */
3232  ut_ad(mutex_own(&dict_sys->mutex));
3233 
3234  pinfo = pars_info_create();
3235 
3236  pars_info_add_str_literal(pinfo, "database_name", database_name);
3237  pars_info_add_str_literal(pinfo, "table_name", table_name);
3238 
3239  ret = dict_stats_exec_sql(
3240  pinfo,
3241  "PROCEDURE DELETE_FROM_TABLE_STATS () IS\n"
3242  "BEGIN\n"
3243  "DELETE FROM \"" TABLE_STATS_NAME "\" WHERE\n"
3244  "database_name = :database_name AND\n"
3245  "table_name = :table_name;\n"
3246  "END;\n");
3247 
3248  return(ret);
3249 }
3250 
3251 /*********************************************************************/
3257 UNIV_INLINE
3258 dberr_t
3260 /*===============================*/
3261  const char* database_name,
3262  const char* table_name)
3263 {
3264  pars_info_t* pinfo;
3265  dberr_t ret;
3266 
3267 #ifdef UNIV_SYNC_DEBUG
3268  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
3269 #endif /* UNIV_SYNC_DEBUG */
3270  ut_ad(mutex_own(&dict_sys->mutex));
3271 
3272  pinfo = pars_info_create();
3273 
3274  pars_info_add_str_literal(pinfo, "database_name", database_name);
3275  pars_info_add_str_literal(pinfo, "table_name", table_name);
3276 
3277  ret = dict_stats_exec_sql(
3278  pinfo,
3279  "PROCEDURE DELETE_FROM_INDEX_STATS () IS\n"
3280  "BEGIN\n"
3281  "DELETE FROM \"" INDEX_STATS_NAME "\" WHERE\n"
3282  "database_name = :database_name AND\n"
3283  "table_name = :table_name;\n"
3284  "END;\n");
3285 
3286  return(ret);
3287 }
3288 
3289 /*********************************************************************/
3294 UNIV_INTERN
3295 dberr_t
3297 /*==================*/
3298  const char* db_and_table,
3299  char* errstr,
3301  ulint errstr_sz)
3302 {
3303  char db_utf8[MAX_DB_UTF8_LEN];
3304  char table_utf8[MAX_TABLE_UTF8_LEN];
3305  dberr_t ret;
3306 
3307 #ifdef UNIV_SYNC_DEBUG
3308  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
3309 #endif /* UNIV_SYNC_DEBUG */
3310  ut_ad(mutex_own(&dict_sys->mutex));
3311 
3312  /* skip tables that do not contain a database name
3313  e.g. if we are dropping SYS_TABLES */
3314  if (strchr(db_and_table, '/') == NULL) {
3315 
3316  return(DB_SUCCESS);
3317  }
3318 
3319  /* skip innodb_table_stats and innodb_index_stats themselves */
3320  if (strcmp(db_and_table, TABLE_STATS_NAME) == 0
3321  || strcmp(db_and_table, INDEX_STATS_NAME) == 0) {
3322 
3323  return(DB_SUCCESS);
3324  }
3325 
3326  dict_fs2utf8(db_and_table, db_utf8, sizeof(db_utf8),
3327  table_utf8, sizeof(table_utf8));
3328 
3329  ret = dict_stats_delete_from_table_stats(db_utf8, table_utf8);
3330 
3331  if (ret == DB_SUCCESS) {
3332  ret = dict_stats_delete_from_index_stats(db_utf8, table_utf8);
3333  }
3334 
3335  if (ret == DB_STATS_DO_NOT_EXIST) {
3336  ret = DB_SUCCESS;
3337  }
3338 
3339  if (ret != DB_SUCCESS) {
3340 
3341  ut_snprintf(errstr, errstr_sz,
3342  "Unable to delete statistics for table %s.%s: %s. "
3343  "They can be deleted later using "
3344 
3345  "DELETE FROM %s WHERE "
3346  "database_name = '%s' AND "
3347  "table_name = '%s'; "
3348 
3349  "DELETE FROM %s WHERE "
3350  "database_name = '%s' AND "
3351  "table_name = '%s';",
3352 
3353  db_utf8, table_utf8,
3354  ut_strerr(ret),
3355 
3356  INDEX_STATS_NAME_PRINT,
3357  db_utf8, table_utf8,
3358 
3359  TABLE_STATS_NAME_PRINT,
3360  db_utf8, table_utf8);
3361  }
3362 
3363  return(ret);
3364 }
3365 
3366 /*********************************************************************/
3373 UNIV_INLINE
3374 dberr_t
3376 /*=============================*/
3377  const char* old_dbname_utf8,
3378  const char* old_tablename_utf8,
3379  const char* new_dbname_utf8,
3380  const char* new_tablename_utf8)
3381 {
3382  pars_info_t* pinfo;
3383  dberr_t ret;
3384 
3385 #ifdef UNIV_SYNC_DEBUG
3386  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
3387 #endif /* UNIV_SYNC_DEBUG */
3388  ut_ad(mutex_own(&dict_sys->mutex));
3389 
3390  pinfo = pars_info_create();
3391 
3392  pars_info_add_str_literal(pinfo, "old_dbname_utf8", old_dbname_utf8);
3393  pars_info_add_str_literal(pinfo, "old_tablename_utf8", old_tablename_utf8);
3394  pars_info_add_str_literal(pinfo, "new_dbname_utf8", new_dbname_utf8);
3395  pars_info_add_str_literal(pinfo, "new_tablename_utf8", new_tablename_utf8);
3396 
3397  ret = dict_stats_exec_sql(
3398  pinfo,
3399  "PROCEDURE RENAME_IN_TABLE_STATS () IS\n"
3400  "BEGIN\n"
3401  "UPDATE \"" TABLE_STATS_NAME "\" SET\n"
3402  "database_name = :new_dbname_utf8,\n"
3403  "table_name = :new_tablename_utf8\n"
3404  "WHERE\n"
3405  "database_name = :old_dbname_utf8 AND\n"
3406  "table_name = :old_tablename_utf8;\n"
3407  "END;\n");
3408 
3409  return(ret);
3410 }
3411 
3412 /*********************************************************************/
3419 UNIV_INLINE
3420 dberr_t
3422 /*=============================*/
3423  const char* old_dbname_utf8,
3424  const char* old_tablename_utf8,
3425  const char* new_dbname_utf8,
3426  const char* new_tablename_utf8)
3427 {
3428  pars_info_t* pinfo;
3429  dberr_t ret;
3430 
3431 #ifdef UNIV_SYNC_DEBUG
3432  ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
3433 #endif /* UNIV_SYNC_DEBUG */
3434  ut_ad(mutex_own(&dict_sys->mutex));
3435 
3436  pinfo = pars_info_create();
3437 
3438  pars_info_add_str_literal(pinfo, "old_dbname_utf8", old_dbname_utf8);
3439  pars_info_add_str_literal(pinfo, "old_tablename_utf8", old_tablename_utf8);
3440  pars_info_add_str_literal(pinfo, "new_dbname_utf8", new_dbname_utf8);
3441  pars_info_add_str_literal(pinfo, "new_tablename_utf8", new_tablename_utf8);
3442 
3443  ret = dict_stats_exec_sql(
3444  pinfo,
3445  "PROCEDURE RENAME_IN_INDEX_STATS () IS\n"
3446  "BEGIN\n"
3447  "UPDATE \"" INDEX_STATS_NAME "\" SET\n"
3448  "database_name = :new_dbname_utf8,\n"
3449  "table_name = :new_tablename_utf8\n"
3450  "WHERE\n"
3451  "database_name = :old_dbname_utf8 AND\n"
3452  "table_name = :old_tablename_utf8;\n"
3453  "END;\n");
3454 
3455  return(ret);
3456 }
3457 
3458 /*********************************************************************/
3462 UNIV_INTERN
3463 dberr_t
3465 /*====================*/
3466  const char* old_name,
3467  const char* new_name,
3468  char* errstr,
3470  size_t errstr_sz)
3471 {
3472  char old_db_utf8[MAX_DB_UTF8_LEN];
3473  char new_db_utf8[MAX_DB_UTF8_LEN];
3474  char old_table_utf8[MAX_TABLE_UTF8_LEN];
3475  char new_table_utf8[MAX_TABLE_UTF8_LEN];
3476  dberr_t ret;
3477 
3478 #ifdef UNIV_SYNC_DEBUG
3479  ut_ad(!rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
3480 #endif /* UNIV_SYNC_DEBUG */
3481  ut_ad(!mutex_own(&dict_sys->mutex));
3482 
3483  /* skip innodb_table_stats and innodb_index_stats themselves */
3484  if (strcmp(old_name, TABLE_STATS_NAME) == 0
3485  || strcmp(old_name, INDEX_STATS_NAME) == 0
3486  || strcmp(new_name, TABLE_STATS_NAME) == 0
3487  || strcmp(new_name, INDEX_STATS_NAME) == 0) {
3488 
3489  return(DB_SUCCESS);
3490  }
3491 
3492  dict_fs2utf8(old_name, old_db_utf8, sizeof(old_db_utf8),
3493  old_table_utf8, sizeof(old_table_utf8));
3494 
3495  dict_fs2utf8(new_name, new_db_utf8, sizeof(new_db_utf8),
3496  new_table_utf8, sizeof(new_table_utf8));
3497 
3498  rw_lock_x_lock(&dict_operation_lock);
3499  mutex_enter(&dict_sys->mutex);
3500 
3501  ulint n_attempts = 0;
3502  do {
3503  n_attempts++;
3504 
3506  old_db_utf8, old_table_utf8,
3507  new_db_utf8, new_table_utf8);
3508 
3509  if (ret == DB_DUPLICATE_KEY) {
3511  new_db_utf8, new_table_utf8);
3512  }
3513 
3514  if (ret == DB_STATS_DO_NOT_EXIST) {
3515  ret = DB_SUCCESS;
3516  }
3517 
3518  if (ret != DB_SUCCESS) {
3519  mutex_exit(&dict_sys->mutex);
3520  rw_lock_x_unlock(&dict_operation_lock);
3521  os_thread_sleep(200000 /* 0.2 sec */);
3522  rw_lock_x_lock(&dict_operation_lock);
3523  mutex_enter(&dict_sys->mutex);
3524  }
3525  } while ((ret == DB_DEADLOCK
3526  || ret == DB_DUPLICATE_KEY
3527  || ret == DB_LOCK_WAIT_TIMEOUT)
3528  && n_attempts < 5);
3529 
3530  if (ret != DB_SUCCESS) {
3531  ut_snprintf(errstr, errstr_sz,
3532  "Unable to rename statistics from "
3533  "%s.%s to %s.%s in %s: %s. "
3534  "They can be renamed later using "
3535 
3536  "UPDATE %s SET "
3537  "database_name = '%s', "
3538  "table_name = '%s' "
3539  "WHERE "
3540  "database_name = '%s' AND "
3541  "table_name = '%s';",
3542 
3543  old_db_utf8, old_table_utf8,
3544  new_db_utf8, new_table_utf8,
3545  TABLE_STATS_NAME_PRINT,
3546  ut_strerr(ret),
3547 
3548  TABLE_STATS_NAME_PRINT,
3549  new_db_utf8, new_table_utf8,
3550  old_db_utf8, old_table_utf8);
3551  mutex_exit(&dict_sys->mutex);
3552  rw_lock_x_unlock(&dict_operation_lock);
3553  return(ret);
3554  }
3555  /* else */
3556 
3557  n_attempts = 0;
3558  do {
3559  n_attempts++;
3560 
3562  old_db_utf8, old_table_utf8,
3563  new_db_utf8, new_table_utf8);
3564 
3565  if (ret == DB_DUPLICATE_KEY) {
3567  new_db_utf8, new_table_utf8);
3568  }
3569 
3570  if (ret == DB_STATS_DO_NOT_EXIST) {
3571  ret = DB_SUCCESS;
3572  }
3573 
3574  if (ret != DB_SUCCESS) {
3575  mutex_exit(&dict_sys->mutex);
3576  rw_lock_x_unlock(&dict_operation_lock);
3577  os_thread_sleep(200000 /* 0.2 sec */);
3578  rw_lock_x_lock(&dict_operation_lock);
3579  mutex_enter(&dict_sys->mutex);
3580  }
3581  } while ((ret == DB_DEADLOCK
3582  || ret == DB_DUPLICATE_KEY
3583  || ret == DB_LOCK_WAIT_TIMEOUT)
3584  && n_attempts < 5);
3585 
3586  mutex_exit(&dict_sys->mutex);
3587  rw_lock_x_unlock(&dict_operation_lock);
3588 
3589  if (ret != DB_SUCCESS) {
3590  ut_snprintf(errstr, errstr_sz,
3591  "Unable to rename statistics from "
3592  "%s.%s to %s.%s in %s: %s. "
3593  "They can be renamed later using "
3594 
3595  "UPDATE %s SET "
3596  "database_name = '%s', "
3597  "table_name = '%s' "
3598  "WHERE "
3599  "database_name = '%s' AND "
3600  "table_name = '%s';",
3601 
3602  old_db_utf8, old_table_utf8,
3603  new_db_utf8, new_table_utf8,
3604  INDEX_STATS_NAME_PRINT,
3605  ut_strerr(ret),
3606 
3607  INDEX_STATS_NAME_PRINT,
3608  new_db_utf8, new_table_utf8,
3609  old_db_utf8, old_table_utf8);
3610  }
3611 
3612  return(ret);
3613 }
3614 
3615 /* tests @{ */
3616 #ifdef UNIV_COMPILE_TEST_FUNCS
3617 
3618 /* The following unit tests test some of the functions in this file
3619 individually, such testing cannot be performed by the mysql-test framework
3620 via SQL. */
3621 
3622 /* test_dict_table_schema_check() @{ */
3623 void
3624 test_dict_table_schema_check()
3625 {
3626  /*
3627  CREATE TABLE tcheck (
3628  c01 VARCHAR(123),
3629  c02 INT,
3630  c03 INT NOT NULL,
3631  c04 INT UNSIGNED,
3632  c05 BIGINT,
3633  c06 BIGINT UNSIGNED NOT NULL,
3634  c07 TIMESTAMP
3635  ) ENGINE=INNODB;
3636  */
3637  /* definition for the table 'test/tcheck' */
3638  dict_col_meta_t columns[] = {
3639  {"c01", DATA_VARCHAR, 0, 123},
3640  {"c02", DATA_INT, 0, 4},
3641  {"c03", DATA_INT, DATA_NOT_NULL, 4},
3642  {"c04", DATA_INT, DATA_UNSIGNED, 4},
3643  {"c05", DATA_INT, 0, 8},
3644  {"c06", DATA_INT, DATA_NOT_NULL | DATA_UNSIGNED, 8},
3645  {"c07", DATA_INT, 0, 4},
3646  {"c_extra", DATA_INT, 0, 4}
3647  };
3648  dict_table_schema_t schema = {
3649  "test/tcheck",
3650  0 /* will be set individually for each test below */,
3651  columns
3652  };
3653  char errstr[512];
3654 
3655  ut_snprintf(errstr, sizeof(errstr), "Table not found");
3656 
3657  /* prevent any data dictionary modifications while we are checking
3658  the tables' structure */
3659 
3660  mutex_enter(&(dict_sys->mutex));
3661 
3662  /* check that a valid table is reported as valid */
3663  schema.n_cols = 7;
3664  if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
3665  == DB_SUCCESS) {
3666  printf("OK: test.tcheck ok\n");
3667  } else {
3668  printf("ERROR: %s\n", errstr);
3669  printf("ERROR: test.tcheck not present or corrupted\n");
3670  goto test_dict_table_schema_check_end;
3671  }
3672 
3673  /* check columns with wrong length */
3674  schema.columns[1].len = 8;
3675  if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
3676  != DB_SUCCESS) {
3677  printf("OK: test.tcheck.c02 has different length and is "
3678  "reported as corrupted\n");
3679  } else {
3680  printf("OK: test.tcheck.c02 has different length but is "
3681  "reported as ok\n");
3682  goto test_dict_table_schema_check_end;
3683  }
3684  schema.columns[1].len = 4;
3685 
3686  /* request that c02 is NOT NULL while actually it does not have
3687  this flag set */
3688  schema.columns[1].prtype_mask |= DATA_NOT_NULL;
3689  if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
3690  != DB_SUCCESS) {
3691  printf("OK: test.tcheck.c02 does not have NOT NULL while "
3692  "it should and is reported as corrupted\n");
3693  } else {
3694  printf("ERROR: test.tcheck.c02 does not have NOT NULL while "
3695  "it should and is not reported as corrupted\n");
3696  goto test_dict_table_schema_check_end;
3697  }
3698  schema.columns[1].prtype_mask &= ~DATA_NOT_NULL;
3699 
3700  /* check a table that contains some extra columns */
3701  schema.n_cols = 6;
3702  if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
3703  == DB_SUCCESS) {
3704  printf("ERROR: test.tcheck has more columns but is not "
3705  "reported as corrupted\n");
3706  goto test_dict_table_schema_check_end;
3707  } else {
3708  printf("OK: test.tcheck has more columns and is "
3709  "reported as corrupted\n");
3710  }
3711 
3712  /* check a table that has some columns missing */
3713  schema.n_cols = 8;
3714  if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
3715  != DB_SUCCESS) {
3716  printf("OK: test.tcheck has missing columns and is "
3717  "reported as corrupted\n");
3718  } else {
3719  printf("ERROR: test.tcheck has missing columns but is "
3720  "reported as ok\n");
3721  goto test_dict_table_schema_check_end;
3722  }
3723 
3724  /* check non-existent table */
3725  schema.table_name = "test/tcheck_nonexistent";
3726  if (dict_table_schema_check(&schema, errstr, sizeof(errstr))
3727  != DB_SUCCESS) {
3728  printf("OK: test.tcheck_nonexistent is not present\n");
3729  } else {
3730  printf("ERROR: test.tcheck_nonexistent is present!?\n");
3731  goto test_dict_table_schema_check_end;
3732  }
3733 
3734 test_dict_table_schema_check_end:
3735 
3736  mutex_exit(&(dict_sys->mutex));
3737 }
3738 /* @} */
3739 
3740 /* save/fetch aux macros @{ */
3741 #define TEST_DATABASE_NAME "foobardb"
3742 #define TEST_TABLE_NAME "test_dict_stats"
3743 
3744 #define TEST_N_ROWS 111
3745 #define TEST_CLUSTERED_INDEX_SIZE 222
3746 #define TEST_SUM_OF_OTHER_INDEX_SIZES 333
3747 
3748 #define TEST_IDX1_NAME "tidx1"
3749 #define TEST_IDX1_COL1_NAME "tidx1_col1"
3750 #define TEST_IDX1_INDEX_SIZE 123
3751 #define TEST_IDX1_N_LEAF_PAGES 234
3752 #define TEST_IDX1_N_DIFF1 50
3753 #define TEST_IDX1_N_DIFF1_SAMPLE_SIZE 500
3754 
3755 #define TEST_IDX2_NAME "tidx2"
3756 #define TEST_IDX2_COL1_NAME "tidx2_col1"
3757 #define TEST_IDX2_COL2_NAME "tidx2_col2"
3758 #define TEST_IDX2_COL3_NAME "tidx2_col3"
3759 #define TEST_IDX2_COL4_NAME "tidx2_col4"
3760 #define TEST_IDX2_INDEX_SIZE 321
3761 #define TEST_IDX2_N_LEAF_PAGES 432
3762 #define TEST_IDX2_N_DIFF1 60
3763 #define TEST_IDX2_N_DIFF1_SAMPLE_SIZE 600
3764 #define TEST_IDX2_N_DIFF2 61
3765 #define TEST_IDX2_N_DIFF2_SAMPLE_SIZE 610
3766 #define TEST_IDX2_N_DIFF3 62
3767 #define TEST_IDX2_N_DIFF3_SAMPLE_SIZE 620
3768 #define TEST_IDX2_N_DIFF4 63
3769 #define TEST_IDX2_N_DIFF4_SAMPLE_SIZE 630
3770 /* @} */
3771 
3772 /* test_dict_stats_save() @{ */
3773 void
3774 test_dict_stats_save()
3775 {
3777  dict_index_t index1;
3778  dict_field_t index1_fields[1];
3779  ib_uint64_t index1_stat_n_diff_key_vals[1];
3780  ib_uint64_t index1_stat_n_sample_sizes[1];
3781  dict_index_t index2;
3782  dict_field_t index2_fields[4];
3783  ib_uint64_t index2_stat_n_diff_key_vals[4];
3784  ib_uint64_t index2_stat_n_sample_sizes[4];
3785  dberr_t ret;
3786 
3787  /* craft a dummy dict_table_t */
3788  table.name = (char*) (TEST_DATABASE_NAME "/" TEST_TABLE_NAME);
3789  table.stat_n_rows = TEST_N_ROWS;
3790  table.stat_clustered_index_size = TEST_CLUSTERED_INDEX_SIZE;
3791  table.stat_sum_of_other_index_sizes = TEST_SUM_OF_OTHER_INDEX_SIZES;
3792  UT_LIST_INIT(table.indexes);
3793  UT_LIST_ADD_LAST(indexes, table.indexes, &index1);
3794  UT_LIST_ADD_LAST(indexes, table.indexes, &index2);
3795  ut_d(table.magic_n = DICT_TABLE_MAGIC_N);
3796  ut_d(index1.magic_n = DICT_INDEX_MAGIC_N);
3797 
3798  index1.name = TEST_IDX1_NAME;
3799  index1.table = &table;
3800  index1.cached = 1;
3801  index1.n_uniq = 1;
3802  index1.fields = index1_fields;
3803  index1.stat_n_diff_key_vals = index1_stat_n_diff_key_vals;
3804  index1.stat_n_sample_sizes = index1_stat_n_sample_sizes;
3805  index1.stat_index_size = TEST_IDX1_INDEX_SIZE;
3806  index1.stat_n_leaf_pages = TEST_IDX1_N_LEAF_PAGES;
3807  index1_fields[0].name = TEST_IDX1_COL1_NAME;
3808  index1_stat_n_diff_key_vals[0] = TEST_IDX1_N_DIFF1;
3809  index1_stat_n_sample_sizes[0] = TEST_IDX1_N_DIFF1_SAMPLE_SIZE;
3810 
3811  ut_d(index2.magic_n = DICT_INDEX_MAGIC_N);
3812  index2.name = TEST_IDX2_NAME;
3813  index2.table = &table;
3814  index2.cached = 1;
3815  index2.n_uniq = 4;
3816  index2.fields = index2_fields;
3817  index2.stat_n_diff_key_vals = index2_stat_n_diff_key_vals;
3818  index2.stat_n_sample_sizes = index2_stat_n_sample_sizes;
3819  index2.stat_index_size = TEST_IDX2_INDEX_SIZE;
3820  index2.stat_n_leaf_pages = TEST_IDX2_N_LEAF_PAGES;
3821  index2_fields[0].name = TEST_IDX2_COL1_NAME;
3822  index2_fields[1].name = TEST_IDX2_COL2_NAME;
3823  index2_fields[2].name = TEST_IDX2_COL3_NAME;
3824  index2_fields[3].name = TEST_IDX2_COL4_NAME;
3825  index2_stat_n_diff_key_vals[0] = TEST_IDX2_N_DIFF1;
3826  index2_stat_n_diff_key_vals[1] = TEST_IDX2_N_DIFF2;
3827  index2_stat_n_diff_key_vals[2] = TEST_IDX2_N_DIFF3;
3828  index2_stat_n_diff_key_vals[3] = TEST_IDX2_N_DIFF4;
3829  index2_stat_n_sample_sizes[0] = TEST_IDX2_N_DIFF1_SAMPLE_SIZE;
3830  index2_stat_n_sample_sizes[1] = TEST_IDX2_N_DIFF2_SAMPLE_SIZE;
3831  index2_stat_n_sample_sizes[2] = TEST_IDX2_N_DIFF3_SAMPLE_SIZE;
3832  index2_stat_n_sample_sizes[3] = TEST_IDX2_N_DIFF4_SAMPLE_SIZE;
3833 
3834  ret = dict_stats_save(&table);
3835 
3836  ut_a(ret == DB_SUCCESS);
3837 
3838  printf("\nOK: stats saved successfully, now go ahead and read "
3839  "what's inside %s and %s:\n\n",
3840  TABLE_STATS_NAME_PRINT,
3841  INDEX_STATS_NAME_PRINT);
3842 
3843  printf("SELECT COUNT(*) = 1 AS table_stats_saved_successfully\n"
3844  "FROM %s\n"
3845  "WHERE\n"
3846  "database_name = '%s' AND\n"
3847  "table_name = '%s' AND\n"
3848  "n_rows = %d AND\n"
3849  "clustered_index_size = %d AND\n"
3850  "sum_of_other_index_sizes = %d;\n"
3851  "\n",
3852  TABLE_STATS_NAME_PRINT,
3853  TEST_DATABASE_NAME,
3854  TEST_TABLE_NAME,
3855  TEST_N_ROWS,
3856  TEST_CLUSTERED_INDEX_SIZE,
3857  TEST_SUM_OF_OTHER_INDEX_SIZES);
3858 
3859  printf("SELECT COUNT(*) = 3 AS tidx1_stats_saved_successfully\n"
3860  "FROM %s\n"
3861  "WHERE\n"
3862  "database_name = '%s' AND\n"
3863  "table_name = '%s' AND\n"
3864  "index_name = '%s' AND\n"
3865  "(\n"
3866  " (stat_name = 'size' AND stat_value = %d AND"
3867  " sample_size IS NULL) OR\n"
3868  " (stat_name = 'n_leaf_pages' AND stat_value = %d AND"
3869  " sample_size IS NULL) OR\n"
3870  " (stat_name = 'n_diff_pfx01' AND stat_value = %d AND"
3871  " sample_size = '%d' AND stat_description = '%s')\n"
3872  ");\n"
3873  "\n",
3874  INDEX_STATS_NAME_PRINT,
3875  TEST_DATABASE_NAME,
3876  TEST_TABLE_NAME,
3877  TEST_IDX1_NAME,
3878  TEST_IDX1_INDEX_SIZE,
3879  TEST_IDX1_N_LEAF_PAGES,
3880  TEST_IDX1_N_DIFF1,
3881  TEST_IDX1_N_DIFF1_SAMPLE_SIZE,
3882  TEST_IDX1_COL1_NAME);
3883 
3884  printf("SELECT COUNT(*) = 6 AS tidx2_stats_saved_successfully\n"
3885  "FROM %s\n"
3886  "WHERE\n"
3887  "database_name = '%s' AND\n"
3888  "table_name = '%s' AND\n"
3889  "index_name = '%s' AND\n"
3890  "(\n"
3891  " (stat_name = 'size' AND stat_value = %d AND"
3892  " sample_size IS NULL) OR\n"
3893  " (stat_name = 'n_leaf_pages' AND stat_value = %d AND"
3894  " sample_size IS NULL) OR\n"
3895  " (stat_name = 'n_diff_pfx01' AND stat_value = %d AND"
3896  " sample_size = '%d' AND stat_description = '%s') OR\n"
3897  " (stat_name = 'n_diff_pfx02' AND stat_value = %d AND"
3898  " sample_size = '%d' AND stat_description = '%s,%s') OR\n"
3899  " (stat_name = 'n_diff_pfx03' AND stat_value = %d AND"
3900  " sample_size = '%d' AND stat_description = '%s,%s,%s') OR\n"
3901  " (stat_name = 'n_diff_pfx04' AND stat_value = %d AND"
3902  " sample_size = '%d' AND stat_description = '%s,%s,%s,%s')\n"
3903  ");\n"
3904  "\n",
3905  INDEX_STATS_NAME_PRINT,
3906  TEST_DATABASE_NAME,
3907  TEST_TABLE_NAME,
3908  TEST_IDX2_NAME,
3909  TEST_IDX2_INDEX_SIZE,
3910  TEST_IDX2_N_LEAF_PAGES,
3911  TEST_IDX2_N_DIFF1,
3912  TEST_IDX2_N_DIFF1_SAMPLE_SIZE, TEST_IDX2_COL1_NAME,
3913  TEST_IDX2_N_DIFF2,
3914  TEST_IDX2_N_DIFF2_SAMPLE_SIZE,
3915  TEST_IDX2_COL1_NAME, TEST_IDX2_COL2_NAME,
3916  TEST_IDX2_N_DIFF3,
3917  TEST_IDX2_N_DIFF3_SAMPLE_SIZE,
3918  TEST_IDX2_COL1_NAME, TEST_IDX2_COL2_NAME, TEST_IDX2_COL3_NAME,
3919  TEST_IDX2_N_DIFF4,
3920  TEST_IDX2_N_DIFF4_SAMPLE_SIZE,
3921  TEST_IDX2_COL1_NAME, TEST_IDX2_COL2_NAME, TEST_IDX2_COL3_NAME,
3922  TEST_IDX2_COL4_NAME);
3923 }
3924 /* @} */
3925 
3926 /* test_dict_stats_fetch_from_ps() @{ */
3927 void
3928 test_dict_stats_fetch_from_ps()
3929 {
3931  dict_index_t index1;
3932  ib_uint64_t index1_stat_n_diff_key_vals[1];
3933  ib_uint64_t index1_stat_n_sample_sizes[1];
3934  dict_index_t index2;
3935  ib_uint64_t index2_stat_n_diff_key_vals[4];
3936  ib_uint64_t index2_stat_n_sample_sizes[4];
3937  dberr_t ret;
3938 
3939  /* craft a dummy dict_table_t */
3940  table.name = (char*) (TEST_DATABASE_NAME "/" TEST_TABLE_NAME);
3941  UT_LIST_INIT(table.indexes);
3942  UT_LIST_ADD_LAST(indexes, table.indexes, &index1);
3943  UT_LIST_ADD_LAST(indexes, table.indexes, &index2);
3944  ut_d(table.magic_n = DICT_TABLE_MAGIC_N);
3945 
3946  index1.name = TEST_IDX1_NAME;
3947  ut_d(index1.magic_n = DICT_INDEX_MAGIC_N);
3948  index1.cached = 1;
3949  index1.n_uniq = 1;
3950  index1.stat_n_diff_key_vals = index1_stat_n_diff_key_vals;
3951  index1.stat_n_sample_sizes = index1_stat_n_sample_sizes;
3952 
3953  index2.name = TEST_IDX2_NAME;
3954  ut_d(index2.magic_n = DICT_INDEX_MAGIC_N);
3955  index2.cached = 1;
3956  index2.n_uniq = 4;
3957  index2.stat_n_diff_key_vals = index2_stat_n_diff_key_vals;
3958  index2.stat_n_sample_sizes = index2_stat_n_sample_sizes;
3959 
3960  ret = dict_stats_fetch_from_ps(&table);
3961 
3962  ut_a(ret == DB_SUCCESS);
3963 
3964  ut_a(table.stat_n_rows == TEST_N_ROWS);
3965  ut_a(table.stat_clustered_index_size == TEST_CLUSTERED_INDEX_SIZE);
3967  == TEST_SUM_OF_OTHER_INDEX_SIZES);
3968 
3969  ut_a(index1.stat_index_size == TEST_IDX1_INDEX_SIZE);
3970  ut_a(index1.stat_n_leaf_pages == TEST_IDX1_N_LEAF_PAGES);
3971  ut_a(index1_stat_n_diff_key_vals[0] == TEST_IDX1_N_DIFF1);
3972  ut_a(index1_stat_n_sample_sizes[0] == TEST_IDX1_N_DIFF1_SAMPLE_SIZE);
3973 
3974  ut_a(index2.stat_index_size == TEST_IDX2_INDEX_SIZE);
3975  ut_a(index2.stat_n_leaf_pages == TEST_IDX2_N_LEAF_PAGES);
3976  ut_a(index2_stat_n_diff_key_vals[0] == TEST_IDX2_N_DIFF1);
3977  ut_a(index2_stat_n_sample_sizes[0] == TEST_IDX2_N_DIFF1_SAMPLE_SIZE);
3978  ut_a(index2_stat_n_diff_key_vals[1] == TEST_IDX2_N_DIFF2);
3979  ut_a(index2_stat_n_sample_sizes[1] == TEST_IDX2_N_DIFF2_SAMPLE_SIZE);
3980  ut_a(index2_stat_n_diff_key_vals[2] == TEST_IDX2_N_DIFF3);
3981  ut_a(index2_stat_n_sample_sizes[2] == TEST_IDX2_N_DIFF3_SAMPLE_SIZE);
3982  ut_a(index2_stat_n_diff_key_vals[3] == TEST_IDX2_N_DIFF4);
3983  ut_a(index2_stat_n_sample_sizes[3] == TEST_IDX2_N_DIFF4_SAMPLE_SIZE);
3984 
3985  printf("OK: fetch successful\n");
3986 }
3987 /* @} */
3988 
3989 /* test_dict_stats_all() @{ */
3990 void
3991 test_dict_stats_all()
3992 {
3993  test_dict_table_schema_check();
3994 
3995  test_dict_stats_save();
3996 
3997  test_dict_stats_fetch_from_ps();
3998 }
3999 /* @} */
4000 
4001 #endif /* UNIV_COMPILE_TEST_FUNCS */
4002 /* @} */
4003 
4004 #endif /* UNIV_HOTBACKUP */