一般情况下,对于“查询”的性能优化是针对于一些复杂的语句,并且需要返回大量的数据的场景。但有些情况下,“查一行”也会执行的特别慢,本文介绍可能出引发这种现象的几类,并总结出解决方法。
1. 第一类:查询长时间不返回:
1.1 第1种情况:等MDL锁:
mysql> select * from t where id = 1;
如果查询结果长时间不返回,一般碰到这种情况,大概率是表t被锁住了。
接下来分析原因的时候,一般都是首先执行以下 show processlist
命令,看看当前语句处于什么状态。然后再针对每种状态,去分析它们产生的原因、如何复现,以及如何处理。
手动构造MDL锁的方法:
mysql> lock table runoob_test write;
//对一个表直接上锁。
mysql> show processlist;
+----+-----------------+-----------------+----------+---------+---------+---------------------------------+-----------------------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-----------------+-----------------+----------+---------+---------+---------------------------------+-----------------------------------------------+
| 5 | event_scheduler | localhost | NULL | Daemon | 4256410 | Waiting on empty queue | NULL |
| 55 | root | localhost | runoob | Query | 6 | Waiting for table metadata lock | select * from runoob_test where runoob_id = 1 |
| 56 | root | localhost | runoob | Query | 0 | init | show processlist |
+----+-----------------+-----------------+----------+---------+---------+---------------------------------+-----------------------------------------------+
5 rows in set (0.01 sec)
可以查看到这条语句的状态是 “Waiting for table metadata lock”
mysql> select blocking_pid from sys.schema_table_lock_waits;
+--------------+
| blocking_pid |
+--------------+
| 59 |
+--------------+
1 row in set (0.05 sec)
再通过查询 sys.schema_table_lock_waits 表找到导致占用锁的线程ID,将其处理掉即可解决阻塞问题。
注:
表级锁是Server层级别的锁,行级锁是InnoDB存储引擎级别的锁。
1.2 第2种情况:等flush:
手动触发MySQL执行flush操作的命令:
mysql> flush tables runoob_test with read lock;
正常情况下flush操作会执行的很快,除非它们也被别的线程堵住了。
所以出现“Waiting for table flush”状态的可能情况是:有一个flush tables命令被别的语句阻塞了,然后它又阻塞了当前的select语句。
遇到这种情况时,同样可以使用 show processlist
进行解决:
1.3 第3种情况:等行级锁:
这类情况比较常见,例如两个事务A、B同时运行,当事务B中占有了某一行的写锁,此时如果事务A试图去访问这一行,则事务A会被阻塞,事务A中的查询语句不返回。
如果使用的是MySQL 5.7版本,可以通过 sys.innodb_lock_waits
表查到(MySQL 8.0中似乎无法查到)。
2. 第二类:查询慢:
MySQL提供了“慢查询”日志的功能,可以将超过用户指定的查询时间阈值的查询命令存入到 slow_query 日志中,供用户分析优化查询语句使用。
具体的操作方法是:
(1)方法一: 在配置文件中修改:
[mysqld]
slow_query_log = 1 //=1表示开启慢查询日志
slow_query_log_file = /var/log/mysql/slow-query.log //设置日志的保存路径
long_query_time = 5 //设置判定为慢查询的时间阈值,单位为秒
(Ubuntu中mysql配置文件所在路径:/etc/mysql/mysql.conf.d/mysqld.cnf
)
(2)方法二: 通过client客户端命令操作的方式:
mysql> set global slow_query_log = 1;
mysql> set global slow_qeury_log_file = "/var/log/mysql/slow-query.log";
mysql> set global long_query_time = 5;
使用 set long_query_time = 0
,可以将“慢查询”
查看 mysql-slow.log 慢查询日志中的内容:
select * from runoob_test where runoob_id = 1;
# Time: 2021-08-08T06:12:43.052494Z
# User@Host: root[root] @ localhost [] Id: 60
# Query_time: 0.005899 Lock_time: 0.000000 Rows_sent: 0 Rows_examined: 0
其中,Query_time
表示查询语句执行的时间(单位秒)。
PS: 在MySQL客户端查看服务器中某个配置参数的值:
mysql> show variables like 'slow_query_log';
+----------------+-------+
| Variable_name | Value |
+----------------+-------+
| slow_query_log | ON |
+----------------+-------+
1 row in set (0.01 sec)
mysql> show variables like 'slow_query_log_file';
+---------------------+-------------------------------+
| Variable_name | Value |
+---------------------+-------------------------------+
| slow_query_log_file | /var/log/mysql/mysql-slow.log |
+---------------------+-------------------------------+
1 row in set (0.01 sec)
mysql> show variables like 'long_query_time';
+-----------------+----------+
| Variable_name | Value |
+-----------------+----------+
| long_query_time | 0.000000 |
+-----------------+----------+
1 row in set (0.00 sec)
3. 小结:
本文介绍的内容是执行“查一行”这种操作时可能出现的被锁住和执行慢的例子。
其中,“被锁住”一般是由于被其他线程占用了锁(表锁、行锁)或系统的flush操作被阻塞导致的间接阻塞。定位方法是在问题复现时(查询操作处于阻塞状态时),使用 show processlist
命令,通过 state
字段查看被锁住具体原因(例如Waiting for metadata lock / table flush / statistics
),通过 sys.schema_table_lock_waits
表 和 sys.innodb_lock_wait
表可分别查看到持有表锁 或 行锁 的线程id,从而进一步决定对此线程的处理方式(例如直接kill掉);
“执行慢”的原因有很多,例如在事务中所要查询的行数据的undo log过大(行数据被update更新了100万次,则undo log中将生成100万条行数据的版本),定位“执行慢”的方法是使用 “慢查询日志”,将阈值设置为合理的值,复现问题后通过 slow-query-log 锁定执行慢的语句并对其进行分析。
4. 番外: 关于sys.schema_table_lock_waits和sys.innodb_lock_wait表:
MySQL中自带四个默认的数据库:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
(1)information_schema 库:
information_schema 数据库提供了访问数据库元数据的方式(所谓“元数据”是指关于数据的数据,如数据库名或表名、列的数据类型、访问权限等)。
换句话说,information_schema 是一个信息数据库,它保存着关于MySQL服务器所维护的所有其他数据库的信息。
mysql> show tables;
+---------------------------------------+
| Tables_in_information_schema |
+---------------------------------------+
| ADMINISTRABLE_ROLE_AUTHORIZATIONS |
| APPLICABLE_ROLES |
| CHARACTER_SETS |
| CHECK_CONSTRAINTS |
| COLLATIONS |
| COLLATION_CHARACTER_SET_APPLICABILITY |
| COLUMNS | //提供了表中的列信息,详细表述了某张表的所有列以及每个列的信息,`show columns from schemaname.tablename` 的结果取自于此表
| COLUMNS_EXTENSIONS |
| COLUMN_PRIVILEGES |
| COLUMN_STATISTICS |
| ENABLED_ROLES |
| ENGINES |
| EVENTS |
| FILES |
| INNODB_BUFFER_PAGE |
| INNODB_BUFFER_PAGE_LRU |
| INNODB_BUFFER_POOL_STATS |
| INNODB_CACHED_INDEXES |
| INNODB_CMP |
| INNODB_CMPMEM |
| INNODB_CMPMEM_RESET |
| INNODB_CMP_PER_INDEX |
| INNODB_CMP_PER_INDEX_RESET |
| INNODB_CMP_RESET |
| INNODB_COLUMNS |
| INNODB_DATAFILES |
| INNODB_FIELDS |
| INNODB_FOREIGN |
| INNODB_FOREIGN_COLS |
| INNODB_FT_BEING_DELETED |
| INNODB_FT_CONFIG |
| INNODB_FT_DEFAULT_STOPWORD |
| INNODB_FT_DELETED |
| INNODB_FT_INDEX_CACHE |
| INNODB_FT_INDEX_TABLE |
| INNODB_INDEXES |
| INNODB_METRICS |
| INNODB_SESSION_TEMP_TABLESPACES |
| INNODB_TABLES |
| INNODB_TABLESPACES |
| INNODB_TABLESPACES_BRIEF |
| INNODB_TABLESTATS |
| INNODB_TEMP_TABLE_INFO |
| INNODB_TRX |
| INNODB_VIRTUAL |
| KEYWORDS |
| KEY_COLUMN_USAGE |
| OPTIMIZER_TRACE |
| PARAMETERS |
| PARTITIONS |
| PLUGINS |
| PROCESSLIST |
| PROFILING |
| REFERENTIAL_CONSTRAINTS |
| RESOURCE_GROUPS |
| ROLE_COLUMN_GRANTS |
| ROLE_ROUTINE_GRANTS |
| ROLE_TABLE_GRANTS |
| ROUTINES |
| SCHEMATA |
| SCHEMATA_EXTENSIONS |
| SCHEMA_PRIVILEGES |
| STATISTICS | //提供了关于表索引的信息,`show index from schemaname.tablename`的结果取自于此表
| ST_GEOMETRY_COLUMNS |
| ST_SPATIAL_REFERENCE_SYSTEMS |
| ST_UNITS_OF_MEASURE |
| TABLES | //提供了关于数据库中的表的信息(包括视图),详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息,`show tables from schemaname`的结果取自与此表
| TABLESPACES |
| TABLESPACES_EXTENSIONS |
| TABLES_EXTENSIONS |
| TABLE_CONSTRAINTS |
| TABLE_CONSTRAINTS_EXTENSIONS |
| TABLE_PRIVILEGES |
| TRIGGERS |
| USER_ATTRIBUTES |
| USER_PRIVILEGES |
| VIEWS |
| VIEW_ROUTINE_USAGE |
| VIEW_TABLE_USAGE |
+---------------------------------------+
79 rows in set (0.00 sec)
使用举例:
(1)通过 information_schema.TABLES 表,查看runoob.runoob_test 表信息:
mysql> select * from TABLES where TABLE_NAME = 'runoob_test'\G
*************************** 1. row ***************************
TABLE_CATALOG: def
TABLE_SCHEMA: runoob
TABLE_NAME: runoob_test
TABLE_TYPE: BASE TABLE
ENGINE: InnoDB
VERSION: 10
ROW_FORMAT: Dynamic
TABLE_ROWS: 2
AVG_ROW_LENGTH: 8192
DATA_LENGTH: 16384
MAX_DATA_LENGTH: 0
INDEX_LENGTH: 0
DATA_FREE: 0
AUTO_INCREMENT: 3
CREATE_TIME: 2021-05-30 17:00:02
UPDATE_TIME: NULL
CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
CHECKSUM: NULL
CREATE_OPTIONS:
TABLE_COMMENT:
1 row in set (0.00 sec)
(2)通过 information_schema.COLUMNS 表查看runoob_test表中的列信息:
mysql> show columns from runoob.runoob_test;
+-----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+----------------+
| runoob_id | int | NO | PRI | NULL | auto_increment |
| runoob_title | varchar(100) | NO | | NULL | |
| submission_date | date | YES | | NULL | |
+-----------------+--------------+------+-----+---------+----------------+
3 rows in set (0.01 sec)
(2)mysql 库:
MySQL的核心数据库,主要负责存储数据库的用户、权限设置、关键字等 MySQL自己需要使用的 控制和管理信息。
例如,常用的 在 mysql.user 表中修改root用户的密码。
mysql> use mysql;
Database changed
mysql> show tables;
+------------------------------------------------------+
| Tables_in_mysql |
+------------------------------------------------------+
| columns_priv |
| component |
| db |
| default_roles |
| engine_cost |
| func |
| general_log |
| global_grants |
| gtid_executed |
| help_category |
| help_keyword |
| help_relation |
| help_topic |
| innodb_index_stats |
| innodb_table_stats |
| password_history |
| plugin |
| procs_priv |
| proxies_priv |
| replication_asynchronous_connection_failover |
| replication_asynchronous_connection_failover_managed |
| role_edges |
| server_cost |
| servers |
| slave_master_info |
| slave_relay_log_info |
| slave_worker_info |
| slow_log |
| tables_priv |
| time_zone |
| time_zone_leap_second |
| time_zone_name |
| time_zone_transition |
| time_zone_transition_type |
| user |
+------------------------------------------------------+
35 rows in set (0.01 sec)
(3)performance_shema 库:
主要用于收集数据库服务器性能参数,并且库里表的存储引擎为 PERFORMANCE_SCHEMA,而用户是不能创建存储引擎为PERFORMANCE_SCHEMA的表的(用户能使用的存储引擎包括InnoDB、MyISAM等)。
(4)sys 库:
sys库所有的数据来自于 performance_shema库,sys库的作用是把performance_shema库的复杂度降低,以便让DBA能更好的阅读这个库里的内容,让DBA更快的了解数据库的运行情况。
sys库中就包括前文提到的三个表:
mysql> use sys;
Database changed
mysql> show tables;
+-----------------------------------------------+
| Tables_in_sys |
+-----------------------------------------------+
| innodb_lock_waits |
| processlist |
| schema_table_lock_waits |
+-----------------------------------------------+
101 rows in set (0.00 sec)