Skip to content

Commit 33cf577

Browse files
mysqlonarmdr-m
authored andcommitted
MDEV-25029: Reduce dict_sys mutex contention for read-only workload
In theory, read-only workload shouldn't have anything to do with dict_sys mutex. dict_sys mutex is meant to protect database metadata. But then why does dict_sys mutex shows up as part of a read-only workload? This workload needs to fetch stats for query processing and while reading these stats dict_sys mutex is taken. Is this really needed? No. For the traditional reasons, it was the default global mutex used. Based on 10.6 changes, flow can now use table->lock_mutex to protect update/access of these stats. table mutexes being table specific global contention arising out of dict_sys is reduced. Thanks to Marko Makela for his early suggestion around proposed alternative and review of the draft patch.
1 parent 7947c54 commit 33cf577

File tree

6 files changed

+79
-52
lines changed

6 files changed

+79
-52
lines changed

storage/innobase/dict/dict0dict.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,9 @@ dict_table_close(
334334
if (last_handle && strchr(table->name.m_name, '/') != NULL
335335
&& dict_stats_is_persistent_enabled(table)) {
336336

337+
table->stats_mutex_lock();
337338
dict_stats_deinit(table);
339+
table->stats_mutex_unlock();
338340
}
339341

340342
MONITOR_DEC(MONITOR_TABLE_REFERENCE);

storage/innobase/dict/dict0stats.cc

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,9 @@ dict_stats_table_clone_create(
406406

407407
dict_table_t* t;
408408

409-
t = (dict_table_t*) mem_heap_alloc(heap, sizeof(*t));
409+
t = (dict_table_t*) mem_heap_zalloc(heap, sizeof(*t));
410+
411+
t->stats_mutex_init();
410412

411413
MEM_CHECK_DEFINED(&table->id, sizeof(table->id));
412414
t->id = table->id;
@@ -434,7 +436,7 @@ dict_stats_table_clone_create(
434436

435437
dict_index_t* idx;
436438

437-
idx = (dict_index_t*) mem_heap_alloc(heap, sizeof(*idx));
439+
idx = (dict_index_t*) mem_heap_zalloc(heap, sizeof(*idx));
438440

439441
MEM_CHECK_DEFINED(&index->id, sizeof(index->id));
440442
idx->id = index->id;
@@ -452,7 +454,7 @@ dict_stats_table_clone_create(
452454

453455
idx->n_uniq = index->n_uniq;
454456

455-
idx->fields = (dict_field_t*) mem_heap_alloc(
457+
idx->fields = (dict_field_t*) mem_heap_zalloc(
456458
heap, idx->n_uniq * sizeof(idx->fields[0]));
457459

458460
for (ulint i = 0; i < idx->n_uniq; i++) {
@@ -463,15 +465,15 @@ dict_stats_table_clone_create(
463465
/* hook idx into t->indexes */
464466
UT_LIST_ADD_LAST(t->indexes, idx);
465467

466-
idx->stat_n_diff_key_vals = (ib_uint64_t*) mem_heap_alloc(
468+
idx->stat_n_diff_key_vals = (ib_uint64_t*) mem_heap_zalloc(
467469
heap,
468470
idx->n_uniq * sizeof(idx->stat_n_diff_key_vals[0]));
469471

470-
idx->stat_n_sample_sizes = (ib_uint64_t*) mem_heap_alloc(
472+
idx->stat_n_sample_sizes = (ib_uint64_t*) mem_heap_zalloc(
471473
heap,
472474
idx->n_uniq * sizeof(idx->stat_n_sample_sizes[0]));
473475

474-
idx->stat_n_non_null_key_vals = (ib_uint64_t*) mem_heap_alloc(
476+
idx->stat_n_non_null_key_vals = (ib_uint64_t*) mem_heap_zalloc(
475477
heap,
476478
idx->n_uniq * sizeof(idx->stat_n_non_null_key_vals[0]));
477479
ut_d(idx->magic_n = DICT_INDEX_MAGIC_N);
@@ -494,6 +496,7 @@ dict_stats_table_clone_free(
494496
/*========================*/
495497
dict_table_t* t) /*!< in: dummy table object to free */
496498
{
499+
t->stats_mutex_destroy();
497500
mem_heap_free(t->heap);
498501
}
499502

@@ -510,7 +513,7 @@ dict_stats_empty_index(
510513
{
511514
ut_ad(!(index->type & DICT_FTS));
512515
ut_ad(!dict_index_is_ibuf(index));
513-
dict_sys.assert_locked();
516+
ut_ad(index->table->stats_mutex_is_owner());
514517

515518
ulint n_uniq = index->n_uniq;
516519

@@ -540,7 +543,9 @@ dict_stats_empty_table(
540543
bool empty_defrag_stats)
541544
/*!< in: whether to empty defrag stats */
542545
{
543-
dict_sys.mutex_lock();
546+
/* Initialize table/index level stats is now protected by
547+
table level lock_mutex.*/
548+
table->stats_mutex_lock();
544549

545550
/* Zero the stats members */
546551
table->stat_n_rows = 0;
@@ -566,7 +571,7 @@ dict_stats_empty_table(
566571
}
567572

568573
table->stat_initialized = TRUE;
569-
dict_sys.mutex_unlock();
574+
table->stats_mutex_unlock();
570575
}
571576

572577
/*********************************************************************//**
@@ -665,7 +670,8 @@ dict_stats_copy(
665670
to have the same statistics as if
666671
the table was empty */
667672
{
668-
dict_sys.assert_locked();
673+
ut_ad(src->stats_mutex_is_owner());
674+
ut_ad(dst->stats_mutex_is_owner());
669675

670676
dst->stats_last_recalc = src->stats_last_recalc;
671677
dst->stat_n_rows = src->stat_n_rows;
@@ -790,8 +796,14 @@ dict_stats_snapshot_create(
790796

791797
t = dict_stats_table_clone_create(table);
792798

799+
table->stats_mutex_lock();
800+
ut_d(t->stats_mutex_lock());
801+
793802
dict_stats_copy(t, table, false);
794803

804+
ut_d(t->stats_mutex_unlock());
805+
table->stats_mutex_unlock();
806+
795807
t->stat_persistent = table->stat_persistent;
796808
t->stats_auto_recalc = table->stats_auto_recalc;
797809
t->stats_sample_pages = table->stats_sample_pages;
@@ -836,14 +848,14 @@ dict_stats_update_transient_for_index(
836848
Initialize some bogus index cardinality
837849
statistics, so that the data can be queried in
838850
various means, also via secondary indexes. */
839-
dict_sys.mutex_lock();
851+
index->table->stats_mutex_lock();
840852
dict_stats_empty_index(index, false);
841-
dict_sys.mutex_unlock();
853+
index->table->stats_mutex_unlock();
842854
#if defined UNIV_DEBUG || defined UNIV_IBUF_DEBUG
843855
} else if (ibuf_debug && !dict_index_is_clust(index)) {
844-
dict_sys.mutex_lock();
856+
index->table->stats_mutex_lock();
845857
dict_stats_empty_index(index, false);
846-
dict_sys.mutex_unlock();
858+
index->table->stats_mutex_unlock();
847859
#endif /* UNIV_DEBUG || UNIV_IBUF_DEBUG */
848860
} else {
849861
mtr_t mtr;
@@ -864,9 +876,9 @@ dict_stats_update_transient_for_index(
864876

865877
switch (size) {
866878
case ULINT_UNDEFINED:
867-
dict_sys.mutex_lock();
879+
index->table->stats_mutex_lock();
868880
dict_stats_empty_index(index, false);
869-
dict_sys.mutex_unlock();
881+
index->table->stats_mutex_unlock();
870882
return;
871883
case 0:
872884
/* The root node of the tree is a leaf */
@@ -883,7 +895,7 @@ dict_stats_update_transient_for_index(
883895
index);
884896

885897
if (!stats.empty()) {
886-
dict_sys.mutex_lock();
898+
index->table->stats_mutex_lock();
887899
for (size_t i = 0; i < stats.size(); ++i) {
888900
index->stat_n_diff_key_vals[i]
889901
= stats[i].n_diff_key_vals;
@@ -892,7 +904,7 @@ dict_stats_update_transient_for_index(
892904
index->stat_n_non_null_key_vals[i]
893905
= stats[i].n_non_null_key_vals;
894906
}
895-
dict_sys.mutex_unlock();
907+
index->table->stats_mutex_unlock();
896908
}
897909
}
898910
}
@@ -910,7 +922,7 @@ dict_stats_update_transient(
910922
/*========================*/
911923
dict_table_t* table) /*!< in/out: table */
912924
{
913-
dict_sys.assert_not_locked();
925+
ut_ad(!table->stats_mutex_is_owner());
914926

915927
dict_index_t* index;
916928
ulint sum_of_index_sizes = 0;
@@ -943,9 +955,9 @@ dict_stats_update_transient(
943955

944956
if (dict_stats_should_ignore_index(index)
945957
|| !index->is_readable()) {
946-
dict_sys.mutex_lock();
958+
index->table->stats_mutex_lock();
947959
dict_stats_empty_index(index, false);
948-
dict_sys.mutex_unlock();
960+
index->table->stats_mutex_unlock();
949961
continue;
950962
}
951963

@@ -954,7 +966,7 @@ dict_stats_update_transient(
954966
sum_of_index_sizes += index->stat_index_size;
955967
}
956968

957-
dict_sys.mutex_lock();
969+
table->stats_mutex_lock();
958970

959971
index = dict_table_get_first_index(table);
960972

@@ -972,7 +984,7 @@ dict_stats_update_transient(
972984

973985
table->stat_initialized = TRUE;
974986

975-
dict_sys.mutex_unlock();
987+
table->stats_mutex_unlock();
976988
}
977989

978990
/* @{ Pseudo code about the relation between the following functions
@@ -1930,7 +1942,7 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
19301942
DBUG_PRINT("info", ("index: %s, online status: %d", index->name(),
19311943
dict_index_get_online_status(index)));
19321944

1933-
dict_sys.assert_not_locked(); // this function is slow
1945+
ut_ad(!index->table->stats_mutex_is_owner());
19341946
ut_ad(index->table->get_ref_count());
19351947

19361948
/* Disable update statistic for Rtree */
@@ -2002,14 +2014,14 @@ static index_stats_t dict_stats_analyze_index(dict_index_t* index)
20022014

20032015
mtr.commit();
20042016

2005-
dict_sys.mutex_lock();
2017+
index->table->stats_mutex_lock();
20062018
for (ulint i = 0; i < n_uniq; i++) {
20072019
result.stats[i].n_diff_key_vals = index->stat_n_diff_key_vals[i];
20082020
result.stats[i].n_sample_sizes = total_pages;
20092021
result.stats[i].n_non_null_key_vals = index->stat_n_non_null_key_vals[i];
20102022
}
20112023
result.n_leaf_pages = index->stat_n_leaf_pages;
2012-
dict_sys.mutex_unlock();
2024+
index->table->stats_mutex_unlock();
20132025

20142026
DBUG_RETURN(result);
20152027
}
@@ -2243,13 +2255,13 @@ dict_stats_update_persistent(
22432255
}
22442256

22452257
ut_ad(!dict_index_is_ibuf(index));
2246-
dict_sys.mutex_lock();
2258+
table->stats_mutex_lock();
22472259
dict_stats_empty_index(index, false);
2248-
dict_sys.mutex_unlock();
2260+
table->stats_mutex_unlock();
22492261

22502262
index_stats_t stats = dict_stats_analyze_index(index);
22512263

2252-
dict_sys.mutex_lock();
2264+
table->stats_mutex_lock();
22532265
index->stat_index_size = stats.index_size;
22542266
index->stat_n_leaf_pages = stats.n_leaf_pages;
22552267
for (size_t i = 0; i < stats.stats.size(); ++i) {
@@ -2285,9 +2297,9 @@ dict_stats_update_persistent(
22852297
}
22862298

22872299
if (!(table->stats_bg_flag & BG_STAT_SHOULD_QUIT)) {
2288-
dict_sys.mutex_unlock();
2300+
table->stats_mutex_unlock();
22892301
stats = dict_stats_analyze_index(index);
2290-
dict_sys.mutex_lock();
2302+
table->stats_mutex_lock();
22912303

22922304
index->stat_index_size = stats.index_size;
22932305
index->stat_n_leaf_pages = stats.n_leaf_pages;
@@ -2313,7 +2325,7 @@ dict_stats_update_persistent(
23132325

23142326
dict_stats_assert_initialized(table);
23152327

2316-
dict_sys.mutex_unlock();
2328+
table->stats_mutex_unlock();
23172329

23182330
return(DB_SUCCESS);
23192331
}
@@ -3123,13 +3135,11 @@ dict_stats_update_for_index(
31233135
{
31243136
DBUG_ENTER("dict_stats_update_for_index");
31253137

3126-
dict_sys.assert_not_locked();
3127-
31283138
if (dict_stats_is_persistent_enabled(index->table)) {
31293139

31303140
if (dict_stats_persistent_storage_check(false)) {
31313141
index_stats_t stats = dict_stats_analyze_index(index);
3132-
dict_sys.mutex_lock();
3142+
index->table->stats_mutex_lock();
31333143
index->stat_index_size = stats.index_size;
31343144
index->stat_n_leaf_pages = stats.n_leaf_pages;
31353145
for (size_t i = 0; i < stats.stats.size(); ++i) {
@@ -3142,7 +3152,7 @@ dict_stats_update_for_index(
31423152
}
31433153
index->table->stat_sum_of_other_index_sizes
31443154
+= index->stat_index_size;
3145-
dict_sys.mutex_unlock();
3155+
index->table->stats_mutex_unlock();
31463156

31473157
dict_stats_save(index->table, &index->id);
31483158
DBUG_VOID_RETURN;
@@ -3183,7 +3193,7 @@ dict_stats_update(
31833193
the persistent statistics
31843194
storage */
31853195
{
3186-
dict_sys.assert_not_locked();
3196+
ut_ad(!table->stats_mutex_is_owner());
31873197

31883198
if (!table->is_readable()) {
31893199
return (dict_stats_report_error(table));
@@ -3318,7 +3328,10 @@ dict_stats_update(
33183328
switch (err) {
33193329
case DB_SUCCESS:
33203330

3321-
dict_sys.mutex_lock();
3331+
table->stats_mutex_lock();
3332+
/* t is localized to this thread so no need to
3333+
take stats mutex lock (limiting it to debug only) */
3334+
ut_d(t->stats_mutex_lock());
33223335

33233336
/* Pass reset_ignored_indexes=true as parameter
33243337
to dict_stats_copy. This will cause statictics
@@ -3327,7 +3340,8 @@ dict_stats_update(
33273340

33283341
dict_stats_assert_initialized(table);
33293342

3330-
dict_sys.mutex_unlock();
3343+
ut_d(t->stats_mutex_unlock());
3344+
table->stats_mutex_unlock();
33313345

33323346
dict_stats_table_clone_free(t);
33333347

storage/innobase/handler/ha_innodb.cc

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14044,7 +14044,7 @@ ha_innobase::info_low(
1404414044
ulint stat_clustered_index_size;
1404514045
ulint stat_sum_of_other_index_sizes;
1404614046

14047-
dict_sys.mutex_lock();
14047+
ib_table->stats_mutex_lock();
1404814048

1404914049
ut_a(ib_table->stat_initialized);
1405014050

@@ -14056,7 +14056,7 @@ ha_innobase::info_low(
1405614056
stat_sum_of_other_index_sizes
1405714057
= ib_table->stat_sum_of_other_index_sizes;
1405814058

14059-
dict_sys.mutex_unlock();
14059+
ib_table->stats_mutex_unlock();
1406014060

1406114061
/*
1406214062
The MySQL optimizer seems to assume in a left join that n_rows
@@ -14172,10 +14172,9 @@ ha_innobase::info_low(
1417214172
stats.create_time = (ulong) stat_info.ctime;
1417314173
}
1417414174

14175-
struct Locking {
14176-
Locking() { dict_sys.mutex_lock(); }
14177-
~Locking() { dict_sys.mutex_unlock(); }
14178-
} locking;
14175+
ib_table->stats_mutex_lock();
14176+
auto _ = make_scope_exit([ib_table]() {
14177+
ib_table->stats_mutex_unlock(); });
1417914178

1418014179
ut_a(ib_table->stat_initialized);
1418114180

storage/innobase/handler/i_s.cc

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Created July 18, 2007 Vasil Dimov
5656
#include "fil0fil.h"
5757
#include "fil0crypt.h"
5858
#include "dict0crea.h"
59+
#include "scope.h"
5960

6061
/** The latest successfully looked up innodb_fts_aux_table */
6162
UNIV_INTERN table_id_t innodb_ft_aux_table_id;
@@ -5021,11 +5022,9 @@ i_s_dict_fill_sys_tablestats(
50215022
table->name.m_name));
50225023

50235024
{
5024-
struct Locking
5025-
{
5026-
Locking() { dict_sys.mutex_lock(); }
5027-
~Locking() { dict_sys.mutex_unlock(); }
5028-
} locking;
5025+
table->stats_mutex_lock();
5026+
auto _ = make_scope_exit([table]() {
5027+
table->stats_mutex_unlock(); });
50295028

50305029
OK(fields[SYS_TABLESTATS_INIT]->store(table->stat_initialized,
50315030
true));

0 commit comments

Comments
 (0)