Performance Schema
Prev: monitoring-in-a-reliability-engineering-world Next: operating-system-and-hardware-optimization
The performance schema provides metrics on operations in MySQL.
Querying Performance Schema involves instruments and consumers,
where instruments refer to code we want to capture information about,
and consumers which is a table that stores the information.
Consumer Organization
Current and Historical Data
Here are some groups of tables in performance_schema.
-
*_currentEvents that are occurring on the server at present -
*_historyLast 10 completed events per thread -
*_history_longLast 10,000 completed events per thread, globally
And events:
events_waitsLow-level server waits, such as acquiring mutexesevents_statementsSQL statementsevents_stagesProfile information, such as creating temporary tables or sending dataevents_transactionsTransactions
There are summary tables that hold statistics on each of thesse events.
Resource Consumption
Performance schema is held in memory: The amount of memory it consumes can be toggled as well. Instrumentation has some cost on CPU usage. Some events (like monitoring mutex lock/release) are particularly expensive.
Limitations
- MySQL must support instrumenting it.
- It collects data after the instrument and consumer are enabled.
- It is difficult to free memory.
sys Schema
MySQL 5.7 has a sys schema performance, which contains views over
performance_schema.
Threads
Performance schema can list all threads in the server:
SELECT NAME, THREAD_ID, PROCESSLIST_ID, THREAD_OS_ID
-> FROM performance_schema.threads;Which lists something like this:
+------------------------+-----------+----------------+--------------+
| NAME | THREAD_ID | PROCESSLIST_ID | THREAD_OS_ID |
+------------------------+-----------+----------------+--------------+
| thread/sql/main | 1 | NULL | 797580 |
| thread/innodb/io_ib... | 3 | NULL | 797583 |
| thread/innodb/io_lo... | 4 | NULL | 797584 |
...
| thread/sql/slave_io | 42 | 5 | 797618 |
| thread/sql/slave_sql | 43 | 6 | 797619 |
| thread/sql/event_sc... | 44 | 7 | 797620 |
| thread/sql/signal_h... | 45 | NULL | 797621 |
| thread/mysqlx/accep... | 46 | NULL | 797623 |
| thread/sql/one_conn... | 27823 | 27784 | 797695 |
| thread/sql/compress... | 48 | 9 | 797624 |
+------------------------+-----------+----------------+--------------+
Configuration
Performance schema can be turned on or off by using setting it to ON
or OFF in a configuration file.
Enabling and Disabling Instruments
Instruments can be queried in the setup_instruments table.
mysql> SELECT * FROM performance_schema.setup_instruments
-> WHERE NAME='statement/sql/select'\G
*************************** 1. row ***************************
NAME: statement/sql/select
ENABLED: NO
TIMED: YES
PROPERTIES:
VOLATILITY: 0
DOCUMENTATION: NULLIt is currently turned off, but can be turned on:
mysql> UPDATE performance_schema.setup_instruments
-> SET ENABLED='YES' WHERE NAME='statement/sql/select';
Query OK, 1 rows affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0Or, since this is SQL, turn on all SQL statement instruments
mysql> UPDATE performance_schema.setup_instruments
-> SET ENABLED='YES' WHERE NAME LIKE statement/sql/%';
Query OK, 167 rows affected (0.00 sec)
Rows matched: 167 Changed: 167 Warnings: 0Updating through SQL or a stored proc does not persist past restarts, so enable performance schema in your configuration files.
Enabling and Disabling Consumers
Consumers can be enabled by:
- Updating the
setup_consumerstable in Performance Schema - Using the stored procedures
pssetup_enable_consumerandps_setup disable_consumerin sys schema - Setting the performance-schema-consumer configuration parameter
| Consumer | Description |
|---|---|
events_stages_(current | history | history_long) | Profiling details, such as “Creating tmp table“, “statistics”, or “buffer pool load” |
events_statements_(current | history | history_long) | Statements statistics |
events_transactions_(current | history | history_long) | Transactions |
events_waits_(current | history | history_long) | Waits |
global_instrumentation | Enables or disables global instrumentation. If disabled, no individual parameters are checked and no global or per-thread data is maintained. No individual event is collected. |
thread_instrumentation | Per-thread instrumentation. Only checked if global instrumentation is enabled. If disabled, no per-thread or individual event data is collected. |
statements_digest | Statement digests |
Monitoring can be tuned for specific objects.
Using Performance Schema
| Column | Description | Importance |
|---|---|---|
| CREATED_TMP_DISK_TABLES | The query created this number of disk-based temporary tables. You have two options to resolve this issue: optimize the query or increase maximum size for in-memory temporary tables. | High |
| CREATED_TMP_TABLES | The query created this number of memory-based temporary tables. Use of in-memory temporary tables is not bad per se. However, if the underlying table grows, they may be converted into disk-based tables. It is good to be prepared for such situations in advance. | Medium |
| SELECT_FULL_JOIN | The JOIN performed a full table scan because there is no good index to resolve the query otherwise. You need to reconsider your indexes unless the table is very small. | High |
| SELECT_FULL_RANGE_JOIN | If the JOIN used a range search of the referenced table. | Medium |
| SELECT_RANGE | If the JOIN used a range search to resolve rows in the first table. This is usually not a big issue. | Low |
| SELECT_RANGE_CHECK | If the JOIN is without indexes, which checks for keys after each row. This is a very bad symptom, and you need to reconsider your table indexes if this value is greater than zero. | High |
| SELECT_SCAN | If the JOIN did a full scan of the first table. This is an issue if the table is large. | Medium |
| SORT_MERGE_PASSES | The number of merge passes that the sort has to perform. If the value is greater than zero and the query performance is slow, you may need to increase sort_buffer_size. | Low |
| SORT_RANGE | If the sort was done using ranges. | Low |
| SORT_ROWS | The number of sorted rows. Compare with the value of the returned rows. If the number of sorted rows is higher, you may need to optimize your query. | Medium (see Description) |
| SORT_SCAN | If the sort was done by scanning a table. This is a very bad sign unless you purposely select all rows from the table without using an index. | High |
| NO_INDEX_USED | No index was used to resolve the query. | High, unless tables are small |
| NO_GOOD_INDEX_USED | Index used to resolve the query is not the best. You need to reconsider your indexes if this value is greater than zero. | High |
We can use this table to figure out problematic queries:
Queries that didn’t use a good index:
SELECT THREAD_ID, SQL_TEXT, ROWS_SENT, ROWS_EXAMINED, CREATED_TMP_TABLES,
NO_INDEX_USED, NO_GOOD_INDEX_USED
FROM performance_schema.events_statements_history_long
WHERE NO_INDEX_USED > 0 OR NO_GOOD_INDEX_USED > 0;SELECT THREAD_ID, SQL_TEXT, ROWS_SENT, ROWS_EXAMINED, CREATED_TMP_TABLES,
CREATED_TMP_DISK_TABLES
FROM performance_schema.events_statements_history_long
WHERE CREATED_TMP_TABLES > 0 OR CREATED_TMP_DISK_TABLES > 0;There are other views that are useful for finding statements that require optimization:
| View | Description |
|---|---|
| statement_analysis | A normalized statement view with aggregated statistics, ordered by the total execution time per the normalized statement. Similar to the events_statements_sum mary_by_digest table but less detailed. |
| statements_with_errors_or_warnings | All normalized statements that raised errors or warnings. |
| statements_with_full_table_scans. | All normalized statements that have done a full table |
| scan. | |
| statements_with_runtimes_in_95th_percentile | All normalized statements whose average execution time is in the top 95th percentile. |
| statements_with_sorting | All normalized statements that have done sorts. The view includes all kinds of sorts. |
| statements_with_temp_tables | All normalized statements that used temporary tables. |
Prepared statements can also be instrumented:
| Instrument Class | Description |
|---|---|
| statement/sql/prepare_sql | PREPARE statement in the text protocol (when run via MySQL CLI) |
| statement/sql/execute_sql | EXECUTE statement in the text protocol (when run via MySQL CLI) |
| statement/com/Prepare | PREPARE statement in the binary protocol (if accessed via MySQL C API) |
| statement/com/Execute | EXECUTE statement in the binary protocol (if accessed via MySQL C API |
Event Stages is particularly useful:
| Stage Classes | Description |
|---|---|
| stage/sql/%tmp% | Everything related to the temporary tables. |
| stage/sql/%lock% | Everything related to locks. |
| stage/%/Waiting for% | Everything waiting for a resource. |
| stage/sql/Sending data | This stage should be compared to the number of ROWS_SENT in the statements statistics. If ROWS_SENT is small, a statement spending a lot of time in this stage could mean that it has to create a temporary file or table to resolve intermediary results. This is often followed by filtering the rows before sending data to the client. This is usually a symptom of a badly optimized query. |
| stage/sql/freeing items stage/sql/cleaning up stage/sql/closing tables stage/sql/end | These are stages that clean resources. Unfortunately, they are not detailed enough, and each of them includes more than a single task. If you see that your queries spend a long time in these stages, you most likely hit resource contention due to high concurrency. You need to check your CPU, I/O, and memory usage as well as whether your hardware and MySQL options can handle concurrency that your application creates. |
Examining Read Versus Write Performance
To see the count of all events in performance schema:
mysql> SELECT EVENT_NAME, COUNT(EVENT_NAME)
-> FROM events_statements_history_long
-> GROUP BY EVENT_NAME;
+----------------------+-------------------+
| EVENT_NAME | COUNT(EVENT_NAME) |
+----------------------+-------------------+
| statement/sql/insert | 504 |
| statement/sql/delete | 502 |
| statement/sql/select | 6987 |
| statement/sql/update | 1007 |
| statement/sql/commit | 500 |
| statement/sql/begin | 500 |
+----------------------+-------------------+
6 rows in set (0.03 sec)To see how long each event takes as an aggregate:
mysql> SELECT EVENT_NAME, COUNT(EVENT_NAME),
-> SUM(LOCK_TIME/1000000) AS latency_ms
-> FROM events_statements_history
-> GROUP BY EVENT_NAME ORDER BY latency_ms DESC;
+----------------------------------+-------------------+------------+
| EVENT_NAME | COUNT(EVENT_NAME) | latency_ms |
+----------------------------------+-------------------+------------+
| statement/sql/select | 194 | 7362.0000 |
| statement/sql/update | 33 | 1276.0000 |
| statement/sql/insert | 16 | 599.0000 |
| statement/sql/delete | 16 | 470.0000 |
| statement/sql/show_status | 2 | 176.0000 |
| statement/sql/begin | 4 | 0.0000 |
| statement/sql/commit | 2 | 0.0000 |
| statement/com/Ping | 2 | 0.0000 |
| statement/sql/show_engine_status | 1 | 0.0000 |
+----------------------------------+-------------------+------------+
9 rows in set (0.01 sec)Or the bytes of rows read and written:
mysql> WITH rows_read AS (SELECT SUM(VARIABLE_VALUE) AS rows_read
-> FROM global_status
-> WHERE VARIABLE_NAME IN ('Handler_read_first', 'Handler_read_key',
-> 'Handler_read_next', 'Handler_read_last', 'Handler_read_prev',
-> 'Handler_read_rnd', 'Handler_read_rnd_next')),
-> rows_written AS (SELECT SUM(VARIABLE_VALUE) AS rows_written
-> FROM global_status
-> WHERE VARIABLE_NAME IN ('Handler_write'))
-> SELECT * FROM rows_read, rows_written\G
*************************** 1. row ***************************
rows_read: 169358114082
rows_written: 33038251685
1 row in set (0.00 sec)To find information about Memory usage:
mysql> SELECT EVENT_NAME,
-> CURRENT_NUMBER_OF_BYTES_USED/1024/1024 AS CURRENT_MB,
-> HIGH_NUMBER_OF_BYTES_USED/1024/1024 AS HIGH_MB
-> FROM performance_schema.memory_summary_global_by_event_name
-> WHERE EVENT_NAME LIKE 'memory/innodb/%'
-> ORDER BY CURRENT_NUMBER_OF_BYTES_USED DESC LIMIT 10;
+----------------------------+--------------+--------------+
| EVENT_NAME | CURRENT_MB | HIGH_MB |
+----------------------------+--------------+--------------+
| memory/innodb/buf_buf_pool | 130.68750000 | 130.68750000 |
| memory/innodb/ut0link_buf | 24.00006104 | 24.00006104 |
| memory/innodb/buf0dblwr | 17.07897949 | 24.96951294 |
| memory/innodb/ut0new | 16.07891273 | 16.07891273 |
| memory/innodb/sync0arr | 6.25006866 | 6.25006866 |
| memory/innodb/lock0lock | 4.85086060 | 4.85086060 |
| memory/innodb/ut0pool | 4.00003052 | 4.00003052 |
| memory/innodb/hash0hash | 3.69776917 | 3.69776917 |
| memory/innodb/os0file | 2.60422516 | 3.61988068 |
| memory/innodb/memory | 1.23812866 | 1.42373657 |
+----------------------------+--------------+--------------+
10 rows in set (0,00 sec)You can see the memory used by user:
mysql> SELECT thread_id tid, user,
-> current_allocated ca, total_allocated
-> FROM sys.memory_by_thread_by_current_bytes LIMIT 9;
+-----+----------------------------+------------+-----------------+
| tid | user | ca | total_allocated |
+-----+----------------------------+------------+-----------------+
| 52 | sveta@localhost | 1.36 MiB | 10.18 MiB |
| 1 | sql/main | 1.02 MiB | 4.95 MiB |
| 33 | innodb/clone_gtid_thread | 525.36 KiB | 24.04 MiB |
| 44 | sql/event_scheduler | 145.72 KiB | 4.23 MiB |
| 43 | sql/slave_sql | 48.74 KiB | 142.46 KiB |
| 42 | sql/slave_io | 20.03 KiB | 232.23 KiB |
| 48 | sql/compress_gtid_table | 13.91 KiB | 17.06 KiB |
| 25 | innodb/fts_optimize_thread | 1.92 KiB | 2.00 KiB |
| 34 | innodb/srv_purge_thread | 1.56 KiB | 1.64 KiB |
+-----+----------------------------+------------+-----------------+
9 rows in set (0,03 sec)Or by thread:
mysql> SELECT * FROM sys.memory_by_thread_by_current_bytes
-> ORDER BY current_allocated desc\G
*************************** 1. row ***************************
thread_id: 152
user: lj@127.0.0.1
current_count_used: 325
current_allocated: 36.00 GiB
current_avg_alloc: 113.43 MiB
current_max_alloc: 36.00 GiB
total_allocated: 37.95 GiBOr looking at variables that were changed dynamically:
mysql> SELECT * FROM performance_schema.variables_info
-> WHERE VARIABLE_SOURCE = 'DYNAMIC'\G
*************************** 1. row ***************************
VARIABLE_NAME: foreign_key_checks
VARIABLE_SOURCE: DYNAMIC
VARIABLE_PATH:
MIN_VALUE: 0
MAX_VALUE: 0
SET_TIME: 2021-07-18 03:14:15.560745
SET_USER: NULL
SET_HOST: NULL
*************************** 2. row ***************************
VARIABLE_NAME: sort_buffer_size
VARIABLE_SOURCE: DYNAMIC
VARIABLE_PATH:
MIN_VALUE: 32768
MAX_VALUE: 18446744073709551615
SET_TIME: 2021-07-19 02:37:11.948190
SET_USER: sveta
SET_HOST: localhost
2 rows in set (0,00 sec)Examining Errors
Performance schema provides digest tables:
mysql> USE performance_schema;
mysql> SHOW CREATE TABLE events_errors_summary_global_by_error\G
*************************** 1. row ***************************
Table: events_errors_summary_global_by_error
Create Table: CREATE TABLE `events_errors_summary_global_by_error` (
`ERROR_NUMBER` int DEFAULT NULL,
`ERROR_NAME` varchar(64) DEFAULT NULL,
`SQL_STATE` varchar(5) DEFAULT NULL,
`SUM_ERROR_RAISED` bigint unsigned NOT NULL,
`SUM_ERROR_HANDLED` bigint unsigned NOT NULL,
`FIRST_SEEN` timestamp NULL DEFAULT '0000-00-00 00:00:00',
`LAST_SEEN` timestamp NULL DEFAULT '0000-00-00 00:00:00',
UNIQUE KEY `ERROR_NUMBER` (`ERROR_NUMBER`)
) ENGINE=PERFORMANCE_SCHEMA DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0,00 sec)Prev: monitoring-in-a-reliability-engineering-world Next: operating-system-and-hardware-optimization