Oprofile was widely used for MySQL profiling on Linux in the past. But since 2010 and 2.6.31 Linux kernels another profiler,
perf, gets increasing popularity. It uses performance counters (CPU hardware registers that count hardware events such as instructions executed)
subsystem in Linux.
perf is capable of lightweight profiling. It is included in the Linux
kernel, under
tools/perf (so features available depends on kernel version), and is frequently updated and enhanced.
So, probably
perf is the future of profiling on Linux and it makes sense to discuss its basic usage for profiling MySQL servers. For detailed discussions of features provided, numerous examples (not related to MySQL) and links I suggest to read great posts by
Brendan Gregg, starting from
this one.
I am going to use Ubuntu 14.04 for this basic Howto, with the following
perf version provided by the
linux-tools-common package:
openxs@ao756:~/dbs/maria10.1$ perf --version
perf version 3.13.11-ckt39
openxs@ao756:~/dbs/maria10.1$ dpkg -S `which perf`
linux-tools-common: /usr/bin/perf
On RPM-based systems package name is just
perf. I plan to discuss and show details of (other versions of)
perf usage on CentOS 6.8 and Fedora 25 later.
I'll work with a bit outdated MariaDB 10.1.x (built from source) I have at hand up and running. In case of standard vendor's packages, make sure that you have debug symbols installed (
perf will inform you if some are missing anyway), to get useful results:
openxs@ao756:~$ dpkg -l | grep -i percona | grep dbg
ii percona-server-5.7-dbg 5.7.16-10-1.trusty amd64 Debugging package for Percona Server
Also, ideally binaries that you are profiling should be built with
-fno-omit-frame-pointer option
.
As in the
previous post about basic
oprofile usage, I'll try to start with a primitive test case based on
Bug #39630, for simplicity and to establish basic trust in the tool's outputs, but this time I also want to try profiling some concurrent load more close to a real life, along the lines of
Bug #83912.
Basic two(!) steps to use
perf for studying some problematic period of MySQL server are the following:
-
Assuming the we want to get a profile of mysqld process during some problematic period, let's say in some script, we start recording samples for, let's say, 30 seconds, as a root user or via sudo:
openxs@ao756:~/dbs/maria10.1$ sudo perf record -p `pidof mysqld` sleep 30
[ perf record: Woken up 1 times to write data ]
[ perf record: Captured and wrote 0.052 MB perf.data (~2257 samples) ]
- Samples are collected in the perf.data file in the current directory by default:
openxs@ao756:~/dbs/maria10.1$ ls -l perf.data-rw------- 1 root root 56768 січ 29 13:53 perf.data
When collection completes we can get profiling information using perf report command. If you want to save the result into some file or just review it, add --stdio option. otherwise you end up in a text-based "interactive" user interface of perf, that, while can be useful, may be misleading and weird for beginners. For the 'select benchmark(500000000, 2*2) case I've got:
openxs@ao756:~/dbs/maria10.1$ sudo perf report --stdio
# ========
# captured on: Sun Jan 29 14:01:24 2017
# hostname : ao756
# os release : 3.13.0-100-generic
# perf version : 3.13.11-ckt39
# arch : x86_64
# nrcpus online : 2
# nrcpus avail : 2
# cpudesc : Intel(R) Pentium(R) CPU 987 @ 1.50GHz
# cpuid : GenuineIntel,6,42,7
# total memory : 3861452 kB
# cmdline : /usr/lib/linux-tools-3.13.0-100/perf record -p 4270 sleep 30
# event : name = cycles, type = 0, config = 0x0, config1 = 0x0, config2 = 0x0, e
# HEADER_CPU_TOPOLOGY info available, use -I to display
# HEADER_NUMA_TOPOLOGY info available, use -I to display
# pmu mappings: cpu = 4, software = 1, tracepoint = 2, uncore_cbox_0 = 6, uncore
# ========
#
# Samples: 248 of event 'cycles'
# Event count (approx.): 24811349
#
# Overhead Command Shared Object
# ........ ....... .................. .......................................
#
11.60% mysqld [kernel.kallsyms] [k] update_blocked_averages
8.12% mysqld [kernel.kallsyms] [k] update_cfs_rq_blocked_load
6.98% mysqld [kernel.kallsyms] [k] find_busiest_group
5.76% mysqld [kernel.kallsyms] [k] sys_io_getevents
4.40% mysqld libc-2.19.so [.] memset
3.21% mysqld [kernel.kallsyms] [k] update_curr
3.03% mysqld [kernel.kallsyms] [k] __calc_delta
2.96% mysqld [kernel.kallsyms] [k] put_prev_task_fair
2.46% mysqld [kernel.kallsyms] [k] timerqueue_add
2.12% mysqld [kernel.kallsyms] [k] cpumask_next_and
2.11% mysqld libpthread-2.19.so [.] __pthread_mutex_cond_lock 2.10% mysqld mysqld [.] os_aio_linux_handle(unsigned long,
...
1.09% mysqld mysqld [.] buf_flush_lru_manager_thread
...
Now, the result above looks similar to and as suspicious as the result I've got with
operf (that is also based on perf_events internally) on the same system and MariaDB version in the
previous post. Everyone wonders where is the time spent on multiplication etc in the above? For
operf (see my
comment) I had found out that
system-wide profiling gives expected results. It seems
perf record -p pid also collects samples for single CPU (the one it runs on, or the one running that process actively, or just some one), and this is a problem for multi-threaded programs like
mysqld if they are executed on multiple-core hardware (almost all hardware are like that these days, maybe some virtual machines aside). The solution is simple, add
-a option for
perf record:
-a, --all-cpus
System-wide collection from all CPUs.
and do not refer to
mysqld, you can filter out related results later (for example, with
sudo perf report --stdio | grep mysqld | head -10, or interactively when
--stdio option is not used). Like this:
openxs@ao756:~/dbs/maria10.1$ sudo perf record -a sleep 30
[ perf record: Woken up 15 times to write data ]
[ perf record: Captured and wrote 4.411 MB perf.data (~192717 samples) ]
openxs@ao756:~/dbs/maria10.1$ sudo perf report --stdio
# ========
# captured on: Sun Jan 29 14:27:31 2017
# hostname : ao756
# os release : 3.13.0-100-generic
# perf version : 3.13.11-ckt39
# arch : x86_64
# nrcpus online : 2
...
# Samples: 83K of event 'cycles'
# Event count (approx.): 30621323147
#
# Overhead Command Shared Object
# ........ ............... ............................. ............................................................................................................
#
31.22% mysqld mysqld [.] Item_func_mul::int_op() 16.57% mysqld mysqld [.] Item_func_hybrid_field_type::val_int()
16.51% mysqld mysqld [.] Item_hybrid_func::result_type() const
16.11% mysqld mysqld [.] Item_func_benchmark::val_int() 9.92% mysqld mysqld [.] Item_int::val_int()
3.27% mysqld mysqld [.] Type_handler_int_result::cmp_type() const
3.25% mysqld mysqld [.] Type_handler_int_result::result_type() const
0.27% kworker/0:2 [kernel.kallsyms] [k] aes_encrypt
0.18% compiz i965_dri.so [.] 0x00000000000def10
0.14% swapper [kernel.kallsyms] [k] intel_idle
...
So, system-wide profile provides the information we can trust, even on multi-core systems for multi-threaded programs. If you know some other way, less intrusive way to get profiles that do not miss the details from threads running on other cores, please, tell me. I was NOT able to find it quickly.
By the way, nice interactive
perf top command shows expected results as well when this primitive test runs (it is still a system-wide profile):
Samples: 17K of event 'cycles', Event count (approx.): 5061557268
28,29% mysqld [.] Item_func_mul::int_op()
15,05% mysqld [.] Item_func_benchmark::val_int()
15,01% mysqld [.] Item_hybrid_func::result_type() cons
15,01% mysqld [.] Item_func_hybrid_field_type::val_int
9,12% mysqld [.] Item_int::val_int()
3,34% perf [.] 0x00000000000564db
3,18% mysqld [.] Type_handler_int_result::cmp_type()
2,93% mysqld [.] Type_handler_int_result::result_type
0,96% libc-2.19.so [.] __strstr_sse2
0,61% libc-2.19.so [.] __GI___strcmp_ssse3
0,24% [kernel] [k] kallsyms_expand_symbol.constprop.1
0,21% [kernel] [k] module_get_kallsym
0,20% libc-2.19.so [.] _int_malloc
0,20% libharfbuzz.so.0.927.0 [.] 0x0000000000028051
0,19% i965_dri.so [.] 0x000000000010a9a0
0,18% [kernel] [k] format_decode
0,16% [kernel] [k] vsnprintf
0,15% libc-2.19.so [.] memchr
0,15% libc-2.19.so [.] __strchr_sse2
0,14% [kernel] [k] number.isra.1
0,13% [kernel] [k] memcpy
0,09% libc-2.19.so [.] strlen
Press '?' for help on key bindings
Press '
q' to get out of that screen.
perf record may be also used the same way as
operf, run in foreground and be interrupted with Ctrl-C (examples are from my last night's session on CentOS):
[root@centos ~]# perf record -ag
^C[ perf record: Woken up 63 times to write data ]
[ perf record: Captured and wrote 15.702 MB perf.data (226643 samples) ]
or it can be started in the background and then interrupted with
-SIGINT signal sent to the process:
[root@centos ~]# perf record -ag &[1] 2353
[root@centos ~]# mysql -uroot -e'select benchmark(500000000,2*2);' test
+--------------------------+
| benchmark(500000000,2*2) |
+--------------------------+
| 0 |
+--------------------------+
[root@centos ~]# kill -sigint 2353[ perf record: Woken up 90 times to write data ]
[root@centos ~]# [ perf record: Captured and wrote 22.366 MB perf.data (322362 samples) ]
[1]+ Interrupt perf record -ag
[root@centos ~]# ps aux | grep perf
root 2358 0.0 0.0 103312 864 pts/0 S+ 22:16 0:00 grep perf
You've probably noted additional
-g option in the examples above (explanation pasted from
perf help record/
man perf-record output):
-g
Enables call-graph (stack chain/backtrace) recording.
Another option often used is
-F (like
-F99 or
-F1000):
-F, --freq=
Profile at this frequency.
For
select benchmark(500000000, 2*2) case
-g does not show anything really unexpected or enlightening:
openxs@ao756:~/dbs/maria10.1$ sudo perf record -ag sleep 30
[sudo] password for openxs:
[ perf record: Woken up 64 times to write data ]
[ perf record: Captured and wrote 16.496 MB perf.data (~720730 samples) ]
openxs@ao756:~/dbs/maria10.1$ sudo perf report --stdio
...
# Overhead Command Shared Object
# ........ ............... ............................. ............................................................................................................
#
31.02% mysqld mysqld [.] Item_func_mul::int_op()
|
--- Item_func_mul::int_op()
|
|--94.56%-- Item_func_hybrid_field_type::val_int()
| Item_func_benchmark::val_int()
| Item::send(Protocol*, String*)
| Protocol::send_result_set_row(List<Item>*)
| select_send::send_data(List<Item>&)
| JOIN::exec_inner()
| JOIN::exec()
| mysql_select(THD*, Item***, TABLE_LIST*, unsigned int, List<Item>&, Item*, unsigned int, st_order*, st_order*, Item*, st_order*, unsi
| handle_select(THD*, LEX*, select_result*, unsigned long)
| execute_sqlcom_select(THD*, TABLE_LIST*)
| mysql_execute_command(THD*)
| mysql_parse(THD*, char*, unsigned int, Parser_state*)
| dispatch_command(enum_server_command, THD*, char*, unsigned int)
| do_command(THD*)
| do_handle_one_connection(THD*)
| handle_one_connection
| start_thread
|
--5.44%-- Item_func_benchmark::val_int()
Item::send(Protocol*, String*)
Protocol::send_result_set_row(List<Item>*)
select_send::send_data(List<Item>&)
JOIN::exec_inner()
JOIN::exec()
mysql_select(THD*, Item***, TABLE_LIST*, unsigned int, List<Item>&, Item*, unsigned int, st_order*, st_order*, Item*, st_order*, unsi
handle_select(THD*, LEX*, select_result*, unsigned long)
execute_sqlcom_select(THD*, TABLE_LIST*)
mysql_execute_command(THD*)
mysql_parse(THD*, char*, unsigned int, Parser_state*)
dispatch_command(enum_server_command, THD*, char*, unsigned int)
do_command(THD*)
do_handle_one_connection(THD*)
handle_one_connection
start_thread
...
But we clearly see backtraces/callgraphs.
Note that this tree starts with the on-CPU functions and works back through the
ancestry. This approach is called a "callee based call graph". This can
be flipped by using
-G for an "inverted call graph", or by using the
caller option to
-g/
--call-graph, instead of the
callee default. The hottest (most frequent) stack trace in this
perf call graph occurred
in
29.33% of samples, which is the product of the overhead percentage
and top stack leaf (
31.02%*94.56%, which are relative rates).
perf report can also be run with "
-g graph" to show absolute overhead rates
I'd like to conclude this blog post with profiling system running test load presented in
Bug #83912 (that, in turn, is just one of the observations based on a very complex real life performance problem I am still working on). There we run N+2 long running UPDATEs on N+2 different tables on a system with N cores. While UPDATEs run, sometimes SELECT asking for a single record by PRIMARY KEY for a different table may sometimes run slow, for fractions of second to many seconds.
So, with table to select from,
t0, and other 4 (N+2, N=2 on my netbook used for testing) tables to update,
t1 ...
t4, created:
openxs@ao756:~/dbs/maria10.1$ bin/mysql -uroot test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 21
Server version: 10.1.18-MariaDB MariaDB Server
Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [test]> select count(*) from t0;
+----------+
| count(*) |
+----------+
| 2097152 |
+----------+
1 row in set (9.33 sec)
MariaDB [test]> select * from t0 where id = 13;
+----+------+----------------------+
| id | c1 | c2 |
+----+------+----------------------+
| 13 | 269 | 0.008545639932663777 |
+----+------+----------------------+
1 row in set (0.00 sec)
MariaDB [test]> show table status like 't1'\G
*************************** 1. row ***************************
Name: t1
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 2079632
Avg_row_length: 139
Data_length: 289177600
Max_data_length: 0
Index_length: 0
Data_free: 6291456
Auto_increment: 2424797
Create_time: 2017-01-29 12:31:57
Update_time: NULL
Check_time: NULL
Collation: latin1_swedish_ci
Checksum: NULL
Create_options: stats_persistent=1
Comment:
1 row in set (0.11 sec)
MariaDB [test]> explain select * from t0 where id = 13;
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | t0 | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+------+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
1 row in set (0.00 sec)
MariaDB [test]> explain update t1 set c2 = rand() where id between 2000 and 300000;
+------+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
| 1 | SIMPLE | t1 | range | PRIMARY | PRIMARY | 4 | NULL | 339246 | Using where |
+------+-------------+-------+-------+---------------+---------+---------+------+--------+-------------+
1 row in set (0.00 sec)
I've started 4 (
N+2) threads each updating large part of one of the tables:
openxs@ao756:~/dbs/maria10.1$ for i in `seq 1 4`; do bin/mysqlslap -uroot --no-drop --create-schema=test --number-of-queries=1000 iternations=10 --concurrency=1 --query="update t$i set c2 = rand() where id between 2000 and 300000;" & done
[1] 15513
[2] 15514
[3] 15515
[4] 15516
In the
SHOW PROCESSLIST we see:
...
| 26 | root | localhost | test | Query | 10 | updating | update t2 set c2 = rand() where id between 2000 and 300000 | 0.000 |
| 27 | root | localhost | test | Query | 32 | updating | update t1 set c2 = rand() where id between 2000 and 300000 | 0.000 |
| 28 | root | localhost | test | Query | 5 | updating | update t4 set c2 = rand() where id between 2000 and 300000 | 0.000 |
| 29 | root | localhost | test | Query | 11 | updating | update t3 set c2 = rand() where id between 2000 and 300000 | 0.000 |
...
Let's see with
perf what these threads are doing, so they spend 29 seconds in
updating status. First without
-g:
openxs@ao756:~/dbs/maria10.1$ sudo perf record -a sleep 30[sudo] password for openxs:
[ perf record: Woken up 15 times to write data ]
[ perf record: Captured and wrote 4.260 MB perf.data (~186128 samples) ]
openxs@ao756:~/dbs/maria10.1$ sudo perf report --stdio
...
# Overhead Command Shared Object
# ........ ............... ............................. ............................................................................................................
#
12.50% kworker/0:1 [kernel.kallsyms] [k] aes_encrypt 10.54% kworker/1:1 [kernel.kallsyms] [k] aes_encrypt
9.77% kworker/1:2 [kernel.kallsyms] [k] aes_encrypt
4.81% kworker/0:0 [kernel.kallsyms] [k] aes_encrypt
4.52% mysqld mysqld [.] buf_calc_page_new_checksum(unsigned char const*) 3.64% mysqld mysqld [.] log_block_calc_checksum_innodb(unsigned char const*)
2.22% mysqld mysqld [.] dtoa.constprop.6
2.15% mysqld mysqld [.] trx_undo_report_row_operation(unsigned long, unsigned long, que_thr_t*, dict_index_t*, dtuple_t const*,
2.10% mysqld mysqld [.] quorem
1.78% mysqld mysqld [.] mtr_commit(mtr_t*)
1.41% mysqld mysqld [.] ha_innobase::update_row(unsigned char const*, unsigned char*)
1.15% mysqld mysqld [.] buf_page_optimistic_get(unsigned long, buf_block_t*, unsigned long, char const*, unsigned long, mtr_t*)
1.15% mysqld mysqld [.] buf_page_get_gen(unsigned long, unsigned long, unsigned long, unsigned long, buf_block_t*, unsigned long
1.11% mysqld mysqld [.] row_search_for_mysql(unsigned char*, unsigned long, row_prebuilt_t*, unsigned long, unsigned long)
1.10% mysqld mysqld [.] multadd
...
I am not surprised to see
aes_encrypt in kernel on top of this system-wide profile, as encrupted HDD is used on this netbook (one of Percona security requirements for any hardware ever used for work, even personal) and upodates are surely I/O bound with
mysqld --no-defaults and 128M of buffer pool.
We may want to study some of
mysqld-related entries further with callgraphs generated (
-g):
openxs@ao756:~/dbs/maria10.1$ sudo perf record -ag sleep 20
[ perf record: Woken up 31 times to write data ]
[ perf record: Captured and wrote 8.313 MB perf.data (~363186 samples) ]
openxs@ao756:~/dbs/maria10.1$ sudo perf report --stdio
...
4.29% mysqld mysqld [.] buf_calc_page_new_checksum(unsigned char const*)
|
--- buf_calc_page_new_checksum(unsigned char const*)
|
|--67.15%-- buf_flush_init_for_writing(unsigned char*, void*, unsigned long)
| buf_flush_page(buf_pool_t*, buf_page_t*, buf_flush_t, bool)
| buf_flush_try_neighbors(unsigned long, unsigned long, buf_flush_t, unsigned long, unsigned long)
| |
| |--52.15%-- buf_do_flush_list_batch(buf_pool_t*, unsigned long, unsigned long)
| | buf_flush_list(unsigned long, unsigned long, unsigned long*)
| | buf_flush_page_cleaner_thread
| | start_thread
| |
| --47.85%-- buf_flush_LRU_list_batch(buf_pool_t*, unsigned long, bool, flush_counters_t*)
| buf_flush_batch(buf_pool_t*, buf_flush_t, unsigned long, unsigned long, bool, flush_counters_t*)
| buf_flush_LRU_tail()
| buf_flush_lru_manager_thread
| start_thread
|
--32.85%-- buf_page_is_corrupted(bool, unsigned char const*, unsigned long)
buf_page_io_complete(buf_page_t*)
|
|--82.17%-- fil_aio_wait(unsigned long)
| io_handler_thread
| start_thread
|
--17.83%-- buf_read_page(unsigned long, unsigned long, unsigned long, trx_t*, buf_page_t**)
buf_page_get_gen(unsigned long, unsigned long, unsigned long, unsigned long, buf_block_t*, unsigned long, char const*, uns
|
|--92.31%-- btr_pcur_move_to_next_page(btr_pcur_t*, mtr_t*)
| btr_pcur_move_to_next(btr_pcur_t*, mtr_t*)
| row_search_for_mysql(unsigned char*, unsigned long, row_prebuilt_t*, unsigned long, unsigned long)
| ha_innobase::index_next(unsigned char*)
| handler::ha_index_next(unsigned char*)
| handler::read_range_next()
| handler::multi_range_read_next(void**)
| Mrr_simple_index_reader::get_next(void**)
| DsMrr_impl::dsmrr_next(void**)
| QUICK_RANGE_SELECT::get_next()
| rr_quick(READ_RECORD*)
| mysql_update(THD*, TABLE_LIST*, List<Item>&, List<Item>&, Item*, unsigned int, st_order*, unsigned long long, e
| mysql_execute_command(THD*)
| mysql_parse(THD*, char*, unsigned int, Parser_state*)
| dispatch_command(enum_server_command, THD*, char*, unsigned int)
| do_command(THD*)
| do_handle_one_connection(THD*)
| handle_one_connection
| start_thread
|
|--5.37%-- btr_estimate_n_rows_in_range(dict_index_t*, dtuple_t const*, unsigned long, dtuple_t const*, unsigned long, trx
| ha_innobase::records_in_range(unsigned int, st_key_range*, st_key_range*)
| handler::multi_range_read_info_const(unsigned int, st_range_seq_if*, void*, unsigned int, unsigned int*, unsign
| DsMrr_impl::dsmrr_info_const(unsigned int, st_range_seq_if*, void*, unsigned int, unsigned int*, unsigned int*,
| check_quick_select(PARAM*, unsigned int, bool, SEL_ARG*, bool, unsigned int*, unsigned int*, Cost_estimate*)
| get_key_scans_params(PARAM*, SEL_TREE*, bool, bool, double)
| SQL_SELECT::test_quick_select(THD*, Bitmap<64u>, unsigned long long, unsigned long long, bool, bool, bool)
| mysql_update(THD*, TABLE_LIST*, List<Item>&, List<Item>&, Item*, unsigned int, st_order*, unsigned long long, e
| mysql_execute_command(THD*)
| mysql_parse(THD*, char*, unsigned int, Parser_state*)
| dispatch_command(enum_server_command, THD*, char*, unsigned int)
| do_command(THD*)
| do_handle_one_connection(THD*)
| handle_one_connection
| start_thread
...
1.28% mysqld mysqld [.] ha_innobase::update_row(unsigned char const*, unsigned char*)
|
--- ha_innobase::update_row(unsigned char const*, unsigned char*)
|
|--97.71%-- handler::ha_update_row(unsigned char const*, unsigned char*)
| mysql_update(THD*, TABLE_LIST*, List<Item>&, List<Item>&, Item*, unsigned int, st_order*, unsigned long long, enum_duplicates, bool,
| mysql_execute_command(THD*)
| mysql_parse(THD*, char*, unsigned int, Parser_state*)
| dispatch_command(enum_server_command, THD*, char*, unsigned int)
| do_command(THD*)
| do_handle_one_connection(THD*)
| handle_one_connection
| start_thread |
--2.29%-- mysql_update(THD*, TABLE_LIST*, List<Item>&, List<Item>&, Item*, unsigned int, st_order*, unsigned long long, enum_duplicates, bool,
mysql_execute_command(THD*)
mysql_parse(THD*, char*, unsigned int, Parser_state*)
dispatch_command(enum_server_command, THD*, char*, unsigned int)
do_command(THD*)
do_handle_one_connection(THD*)
handle_one_connection
start_thread
...
Now to the interesting part (slow
UPDATEs when I/O bound are expected). Usually (but not always, in original real life case!) simple
SELECT runs fast, but if we limit
innodb_thread_concurrency to, say, 4, it may start to run slow:
MariaDB [test]> select * from t0 where id = 13;
+----+------+----------------------+
| id | c1 | c2 |
+----+------+----------------------+
| 13 | 269 | 0.008545639932663777 |
+----+------+----------------------+
1 row in set (0.00 sec)
MariaDB [test]> select @@innodb_thread_concurrency;
+-----------------------------+
| @@innodb_thread_concurrency |
+-----------------------------+
| 0 |
+-----------------------------+
1 row in set (0.05 sec)
MariaDB [test]> set global innodb_thread_concurrency=4;
Query OK, 0 rows affected (0.25 sec)
MariaDB [test]> select * from t0 where id = 13;
+----+------+----------------------+
| id | c1 | c2 |
+----+------+----------------------+
| 13 | 269 | 0.008545639932663777 |
+----+------+----------------------+
1 row in set (2.92 sec)
MariaDB [test]> select * from t0 where id = 13;
+----+------+----------------------+
| id | c1 | c2 |
+----+------+----------------------+
| 13 | 269 | 0.008545639932663777 |
+----+------+----------------------+
1 row in set (12.16 sec)
Profile with callgraph may help to find out why this happens:
openxs@ao756:~/dbs/maria10.1$ sudo perf record -ag sleep 20
[ perf record: Woken up 32 times to write data ]
[ perf record: Captured and wrote 8.503 MB perf.data (~371488 samples) ]
openxs@ao756:~/dbs/maria10.1$ sudo perf report --stdio
...
|--1.40%-- sys_select
| system_call_fastpath
| __select
| |
| |--71.70%-- srv_conc_enter_innodb(trx_t*)
| | ha_innobase::index_read(unsigned char*, unsigned char const*, unsigned int, ha_rkey_function)
| | handler::index_read_idx_map(unsigned char*, unsigned int, unsigned char const*, unsigned long, ha_rkey_function)
| | handler::ha_index_read_idx_map(unsigned char*, unsigned int, unsigned char const*, unsigned long, ha_rkey_function)
| | join_read_const(st_join_table*)
| | join_read_const_table(THD*, st_join_table*, st_position*) [clone .isra.301]
| | make_join_statistics(JOIN*, List<TABLE_LIST>&, st_dynamic_array*)
| | JOIN::optimize_inner()
| | JOIN::optimize()
| | mysql_select(THD*, Item***, TABLE_LIST*, unsigned int, List<Item>&, Item*, unsigned int, st_order*, st_order*, Item*, st_o | | handle_select(THD*, LEX*, select_result*, unsigned long)
| | execute_sqlcom_select(THD*, TABLE_LIST*)
| | mysql_execute_command(THD*)
| | mysql_parse(THD*, char*, unsigned int, Parser_state*)
| | dispatch_command(enum_server_command, THD*, char*, unsigned int)
| | do_command(THD*)
| | do_handle_one_connection(THD*)
| | handle_one_connection
| | start_thread
...
It spends 71.7% of time waiting to join InnoDB queue (in this limited InnoDB concurrency case) while diving into the index while optimizing the query... This time is spent on "
statistics" stage, for single row
SELECT by
PRIMARY KEY. Now, go conclude this without a backtrace of calls or some profiler.