MySQL8 中文参考(四十)
10.10 缓冲和缓存
10.10.1 InnoDB 缓冲池优化
10.10.2 MyISAM 键缓存
10.10.3 缓存准备语句和存储程序
MySQL 使用多种策略在内存缓冲区中缓存信息以提高性能。
10.10.1 InnoDB 缓冲池优化
原文:
dev.mysql.com/doc/refman/8.0/en/innodb-buffer-pool-optimization.html
InnoDB维护一个称为缓冲池的存储区域,用于在内存中缓存数据和索引。了解InnoDB缓冲池的工作原理,并利用它将频繁访问的数据保留在内存中,是 MySQL 调优的重要方面。
有关InnoDB缓冲池内部工作原理、LRU 替换算法概述和一般配置信息的解释,请参阅第 17.5.1 节,“缓冲池”。
有关额外的InnoDB缓冲池配置和调优信息,请参阅以下章节:
-
第 17.8.3.4 节,“配置 InnoDB 缓冲池预取(预读)”
-
第 17.8.3.5 节,“配置缓冲池刷新”
-
第 17.8.3.3 节,“使缓冲池具有扫描抵抗性”
-
第 17.8.3.2 节,“配置多个缓冲池实例”
-
第 17.8.3.6 节,“保存和恢复缓冲池状态”
-
第 17.8.3.1 节,“配置 InnoDB 缓冲池大小”
10.10.2 MyISAM 关键缓存
10.10.2.1 共享关键缓存访问
10.10.2.2 多个关键缓存
10.10.2.3 中点插入策略
10.10.2.4 索引预加载
10.10.2.5 关键缓存块大小
10.10.2.6 重构关键缓存
为了最小化磁盘 I/O,MyISAM存储引擎利用了许多数据库管理系统使用的策略。它使用缓存机制将最常访问的表块保留在内存中:
-
对于索引块,维护一个称为关键缓存(或关键缓冲区)的特殊结构。该结构包含多个块缓冲区,其中放置了最常用的索引块。
-
对于数据块,MySQL 不使用特殊缓存。相反,它依赖于本机操作系统文件系统缓存。
本节首先描述了MyISAM关键缓存的基本操作。然后讨论了改进关键缓存性能的特性,以及使您能够更好地控制缓存操作的功能:
-
多个会话可以同时访问缓存。
-
您可以设置多个关键缓存并将表索引分配给特定缓存。
要控制关键缓存的大小,请使用key_buffer_size系统变量。如果将此变量设置为零,则不使用关键缓存。如果key_buffer_size的值太小,无法分配最小数量的块缓冲区(8),则也不使用关键缓存。
当关键缓存不可用时,索引文件仅使用操作系统提供的本机文件系统缓冲区进行访问。(换句话说,表索引块的访问采用与表数据块相同的策略。)
索引块是对MyISAM索引文件的连续访问单元。通常,索引块的大小等于索引 B 树节点的大小。(索引在磁盘上使用 B 树数据结构表示。树底部的节点是叶节点。叶节点上面的节点是非叶节点。)
关键缓存结构中的所有块缓冲区大小相同。这个大小可以等于、大于或小于表索引块的大小。通常这两个值中的一个是另一个的倍数。
当需要访问任何表索引块的数据时,服务器首先检查它是否在关键缓存的某个块缓冲区中可用。如果是,服务器访问关键缓存中的数据而不是在磁盘上。也就是说,它从缓存中读取或写入数据,而不是从磁盘读取或写入数据。否则,服务器选择一个包含不同表索引块(或块)的缓存块缓冲区,并将数据替换为所需表索引块的副本。一旦新的索引块在缓存中,索引数据就可以被访问。
如果选择要替换的块已被修改,该块被视为“脏”。在这种情况下,在替换之前,其内容被刷新到其来源的表索引中。
通常,服务器遵循 LRU(最近最少使用)策略:在选择要替换的块时,它选择最近最少使用的索引块。为了更容易做出这个选择,关键缓存模块维护了一个特殊列表(LRU 链),按使用时间排序所有已使用的块。当访问一个块时,它是最近使用的,并被放在列表的末尾。当需要替换块时,列表开头的块是最近最少使用的,成为首选的驱逐候选。
InnoDB存储引擎也使用 LRU 算法来管理其缓冲池。请参阅第 17.5.1 节,“缓冲池”。
10.10.2.1 共享密钥缓存访问
线程可以同时访问密钥缓存缓冲区,但需满足以下条件:
-
一个未被更新的缓冲区可以被多个会话访问。
-
正在更新的缓冲区会导致需要使用它的会话等待更新完成。
-
多个会话可以发起导致缓存块替换的请求,只要它们不相互干扰(即,只要它们需要不同的索引块,从而导致替换不同的缓存块)。
共享对密钥缓存的访问使服务器能够显著提高吞吐量。
10.10.2.2 多个关键缓存
注意
截至 MySQL 8.0,讨论在此处引用多个MyISAM关键缓存的复合部分结构化变量语法已被弃用。
关键缓存的共享访问可以提高性能,但并不能完全消除会话之间的争用。它们仍然竞争管理对关键缓存缓冲区访问的控制结构。为了进一步减少关键缓存访问的争用,MySQL 还提供了多个关键缓存。此功能使您能够将不同的表索引分配给不同的关键缓存。
当存在多个关键缓存时,服务器必须知道在处理给定MyISAM表的查询时使用哪个缓存。默认情况下,所有MyISAM表索引都缓存在默认关键缓存中。要将表索引分配给特定的关键缓存,请使用CACHE INDEX语句(请参见 Section 15.7.8.2, “CACHE INDEX Statement”)。例如,以下语句将表t1、t2和t3的索引分配给名为hot_cache的关键缓存:
mysql> CACHE INDEX t1, t2, t3 IN hot_cache;
+---------+--------------------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+--------------------+----------+----------+
| test.t1 | assign_to_keycache | status | OK |
| test.t2 | assign_to_keycache | status | OK |
| test.t3 | assign_to_keycache | status | OK |
+---------+--------------------+----------+----------+
在CACHE INDEX语句中引用的关键缓存可以通过使用SET GLOBAL参数设置语句设置其大小,或通过使用服务器启动选项创建。例如:
mysql> SET GLOBAL keycache1.key_buffer_size=128*1024;
要销毁关键缓存,请将其大小设置为零:
mysql> SET GLOBAL keycache1.key_buffer_size=0;
无法销毁默认关键缓存。任何尝试这样做都将被忽略:
mysql> SET GLOBAL key_buffer_size = 0;
mysql> SHOW VARIABLES LIKE 'key_buffer_size';
+-----------------+---------+
| Variable_name | Value |
+-----------------+---------+
| key_buffer_size | 8384512 |
+-----------------+---------+
关键缓存变量是具有名称和组件的结构化系统变量。对于keycache1.key_buffer_size,keycache1是缓存变量名称,key_buffer_size是缓存组件。有关引用结构化关键缓存系统变量的语法描述,请参见 Section 7.1.9.5, “Structured System Variables”。
默认情况下,表索引分配给在服务器启动时创建的主(默认)关键缓存。当关键缓存被销毁时,分配给它的所有索引将重新分配给默认关键缓存。
对于繁忙的服务器,您可以使用涉及三个关键缓存的策略:
-
一个占据分配给所有关键缓存空间 20%的“热”关键缓存。用于经常用于搜索但不更新的表。
-
一个占据分配给所有关键缓存空间 20%的“冷”关键缓存。用于中等大小、经常修改的表,例如临时表。
-
一个占据关键缓存空间 60%的“热”关键缓存。将其作为默认关键缓存,用于默认情况下所有其他表。
使用三个关键缓存是有益的一个原因是,对一个关键缓存结构的访问不会阻塞对其他缓存的访问。访问分配给一个缓存的表的语句不会与访问分配给另一个缓存的表的语句竞争。性能提升也出现在其他方面:
-
热缓存仅用于检索查询,因此其内容永远不会被修改。因此,每当需要从磁盘中拉入一个索引块时,被选中用于替换的缓存块的内容无需首先刷新。
-
对于分配给热缓存的索引,如果没有需要进行索引扫描的查询,那么非叶节点对应的索引 B 树块很可能仍然保留在缓存中。
-
对于临时表最频繁执行的更新操作,当更新的节点在缓存中且无需首先从磁盘读取时,操作速度会快得多。如果临时表的索引大小与冷键缓存的大小相当,那么更新的节点在缓存中的概率非常高。
CACHE INDEX 语句建立了表与关键缓存之间的关联,但每次服务器重新启动时该关联会丢失。如果希望每次服务器启动时关联生效,一种实现方法是使用选项文件:包含配置关键缓存的变量设置,以及一个命名为包含要执行的CACHE INDEX 语句的文件的 init_file 系统变量。例如:
key_buffer_size = 4G
hot_cache.key_buffer_size = 2G
cold_cache.key_buffer_size = 2G
init_file=/*path*/*to*/*data-directory*/mysqld_init.sql
mysqld_init.sql 中的语句在每次服务器启动时执行。该文件应该每行包含一个 SQL 语句。以下示例将几个表分配给了hot_cache和cold_cache:
CACHE INDEX db1.t1, db1.t2, db2.t3 IN hot_cache
CACHE INDEX db1.t4, db2.t5, db2.t6 IN cold_cache
10.10.2.3 中点插入策略
默认情况下,键缓存管理系统使用简单的 LRU 策略来选择要驱逐的键缓存块,但它还支持一种更复杂的称为中点插入策略的方法。
当使用中点插入策略时,LRU 链被分为两部分:热子列表和温热子列表。两部分之间的分界点不是固定的,但键缓存管理系统确保温热部分不会“太短”,始终至少包含key_cache_division_limit的百分比的键缓存块。key_cache_division_limit是结构化键缓存变量的组成部分,因此它的值是可以针对每个缓存设置的参数。
当从表中读取索引块到键缓存时,它被放置在温热子列表的末尾。在一定数量的命中(对块的访问)之后,它将被提升到热子列表。目前,提升块所需的命中次数(3)对所有索引块都是相同的。
提升到热子列表的块被放置在列表的末尾。然后,该块在该子列表内循环。如果该块在子列表的开头停留足够长的时间,它将被降级到温热子列表。这个时间由键缓存的key_cache_age_threshold组件的值确定。
阈值值规定,对于包含*N*块的键缓存,未在最后*N* * key_cache_age_threshold / 100次命中内访问的热子列表开头的块将移动到温热子列表的开头。然后它成为驱逐的第一个候选,因为替换块总是从温热子列表的开头取出。
中点插入策略使您能够始终将更有价值的块保留在缓存中。如果您更喜欢使用普通的 LRU 策略,请将key_cache_division_limit的值保持默认值 100。
中点插入策略有助于提高性能,当执行需要索引扫描的查询时,有效地将与有价值的高级 B 树节点对应的所有索引块推出缓存。为了避免这种情况,您必须使用中点插入策略,并将key_cache_division_limit设置为远低于 100。然后,在索引扫描操作期间,频繁命中的有价值节点将在热子列表中保留。
10.10.2.4 索引预加载
如果关键缓存中有足够的块来容纳整个索引的块,或者至少是对应于其非叶节点的块,那么在开始使用之前将关键缓存预加载索引块是有意义的。预加载使您能够以最有效的方式将表索引块放入关键缓存缓冲区中:通过按顺序从磁盘读取索引块。
没有预加载,块仍然根据查询的需要放入关键缓存中。尽管这些块保留在缓存中,因为所有这些块都有足够的缓冲区,它们是以随机顺序而不是顺序地从磁盘中获取的。
要将索引预加载到缓存中,请使用LOAD INDEX INTO CACHE语句。例如,以下语句预加载了表t1和t2的索引的节点(索引块):
mysql> LOAD INDEX INTO CACHE t1, t2 IGNORE LEAVES;
+---------+--------------+----------+----------+
| Table | Op | Msg_type | Msg_text |
+---------+--------------+----------+----------+
| test.t1 | preload_keys | status | OK |
| test.t2 | preload_keys | status | OK |
+---------+--------------+----------+----------+
IGNORE LEAVES修饰符导致只预加载索引的非叶节点的块。因此,所示语句预加载了来自t1的所有索引块,但仅预加载了来自t2的非叶节点的块。
如果使用CACHE INDEX语句将索引分配给关键缓存,预加载将索引块放入该缓存中。否则,索引将加载到默认关键缓存中。
原文:
dev.mysql.com/doc/refman/8.0/en/key-cache-block-size.html
10.10.2.5 关键缓存块大小
可以使用key_cache_block_size变量来指定单个关键缓存的块缓冲区大小。这允许调整索引文件的 I/O 操作性能。
当读取缓冲区的大小等于本机操作系统 I/O 缓冲区的大小时,I/O 操作的最佳性能可以实现。但是,将关键节点的大小设置为 I/O 缓冲区的大小并不总是确保获得最佳的整体性能。当读取大叶节点时,服务器会拉取许多不必要的数据,有效地阻止了对其他叶节点的读取。
要控制MyISAM表的.MYI索引文件中块的大小,请在服务器启动时使用--myisam-block-size选项。
原文:
dev.mysql.com/doc/refman/8.0/en/key-cache-restructuring.html
10.10.2.6 重构关键缓存
可通过更新其参数值随时重构关键缓存。例如:
mysql> SET GLOBAL cold_cache.key_buffer_size=4*1024*1024;
如果您为key_buffer_size或key_cache_block_size关键缓存组件分配一个与组件当前值不同的值,则服务器会销毁缓存的旧结构,并根据新值创建一个新结构。如果缓存包含任何脏块,服务器会在销毁和重新创建缓存之前将它们保存到磁盘。如果更改其他关键缓存参数,则不会发生重构。
重构关键缓存时,服务器首先将任何脏缓冲区的内容刷新到磁盘。之后,缓存内容变得不可用。然而,重构不会阻塞需要使用分配给缓存的索引的查询。相反,服务器直接使用本地文件系统缓存访问表索引。文件系统缓存不如使用关键缓存高效,因此虽然查询会执行,但可以预期会有减速。在缓存重构后,它再次可用于缓存分配给它的索引,并且索引的文件系统缓存使用停止。
10.10.3 预处理语句和存储程序的缓存
对于客户端可能在会话期间多次执行的某些语句,服务器会将语句转换为内部结构并缓存该结构以在执行期间使用。缓存使服务器能够更高效地执行,因为它避免了在会话期间再次需要语句时重新转换语句的开销。对这些语句进行转换和缓存:
-
预处理语句,无论是在 SQL 级别(使用
PREPARE语句)处理的还是使用二进制客户端/服务器协议(使用mysql_stmt_prepare()C API 函数)处理的。max_prepared_stmt_count系统变量控制服务器缓存的语句总数。(所有会话中预处理语句数量的总和。) -
存储程序(存储过程和函数、触发器和事件)。在这种情况下,服务器会转换并缓存整个程序体。
stored_program_cache系统变量指示服务器每个会话缓存的存储程序的大致数量。
服务器在每个会话基础上维护预处理语句和存储程序的缓存。为一个会话缓存的语句对其他会话不可访问。当会话结束时,服务器会丢弃为其缓存的任何语句。
当服务器使用缓存的内部语句结构时,必须注意结构不会过时。对象的元数据更改可能会导致语句使用的对象的当前定义与内部语句结构中表示的定义不匹配。元数据更改会发生在 DDL 语句中,比如创建、删除、修改、重命名或截断表,或者分析、优化或修复表。表内容的更改(例如,使用INSERT或UPDATE)不会更改元数据,也不会更改SELECT语句。
这里是问题的示例。假设客户端准备了这个语句:
PREPARE s1 FROM 'SELECT * FROM t1';
SELECT *在内部结构中扩展为表中的列列表。如果使用ALTER TABLE修改表中的列集,那么预处理语句就会过时。如果服务器在客户端下次执行s1时没有检测到这个更改,预处理语句将返回不正确的结果。
为了避免由于预处理语句引用的表或视图的元数据更改而引起的问题,服务器会检测这些更改,并在下次执行时自动重新准备该语句。也就是说,服务器会重新解析该语句并重建内部结构。在引用的表或视图从表定义缓存中刷新时,也会重新解析,无论是隐式地为新条目腾出缓存空间,还是显式地由于FLUSH TABLES。
类似地,如果存储程序使用的对象发生更改,服务器会重新解析程序中受影响的语句。
服务器还会检测表达式中对象的元数据更改。这些对象可能被存储程序特定语句使用,例如DECLARE CURSOR或流程控制语句,如IF,CASE和RETURN。
为了避免重新解析整个存储程序,服务器仅在需要时重新解析程序中受影响的语句或表达式。例如:
-
假设表或视图的元数据发生更改。程序中访问该表或视图的
SELECT *将重新解析,但不会重新解析不访问该表或视图的SELECT *。 -
当语句受到影响时,服务器会尽可能部分重新解析它。考虑这个
CASE语句:CASE *case_expr* WHEN *when_expr1* ... WHEN *when_expr2* ... WHEN *when_expr3* ... ... END CASE如果元数据更改仅影响
WHEN *when_expr3*,那么该表达式将被重新解析。*case_expr*和其他WHEN表达式不会被重新解析。
重新解析使用了最初转换为内部形式时生效的默认数据库和 SQL 模式。
服务器尝试重新解析最多三次。如果所有尝试都失败,则会发生错误。
重新解析是自动的,但在发生时,会降低预处理语句和存储程序的性能。
对于预处理语句,Com_stmt_reprepare状态变量跟踪重新准备的次数。
10.11 优化锁操作
10.11.1 内部锁定方法
10.11.2 表锁定问题
10.11.3 并发插入
10.11.4 元数据锁定
10.11.5 外部锁定
MySQL 使用锁定来管理对表内容的争用:
-
内部锁定是在 MySQL 服务器内部执行的,通过多个线程管理对表内容的争用。这种类型的锁定是内部的,因为它完全由服务器执行,不涉及其他程序。参见第 10.11.1 节,“内部锁定方法”。
-
外部锁定发生在服务器和其他程序锁定
MyISAM表文件以协调它们之间哪个程序可以在哪个时间访问表。参见第 10.11.5 节,“外部锁定”。
10.11.1 内部锁定方法
本节讨论内部锁定;即 MySQL 服务器内部执行的锁定,以管理多个会话对表内容的争用。这种类型的锁定是内部的,因为它完全由服务器执行,不涉及其他程序。有关其他程序在 MySQL 文件上执行的锁定,请参见第 10.11.5 节,“外部锁定”。
-
行级锁定
-
表级锁定
-
选择锁定类型
行级锁定
MySQL 对InnoDB表使用行级锁定,以支持多个会话同时写入访问,使其适用于多用户、高并发和 OLTP 应用程序。
为了避免在单个InnoDB表上执行多个并发写操作时发生死锁,在事务开始时通过为预计修改的每组行发出SELECT ... FOR UPDATE语句来获取必要的锁,即使数据更改语句在事务后面执行。如果事务修改或锁定多个表,请在每个事务内以相同顺序发出适用的语句。死锁会影响性能而不是表示严重错误,因为InnoDB默认会自动检测死锁条件并回滚受影响的事务之一。
在高并发系统中,死锁检测可能导致许多线程等待相同锁时出现减速。有时,禁用死锁检测并依赖于innodb_lock_wait_timeout设置在死锁发生时回滚事务可能更有效。可以使用innodb_deadlock_detect配置选项禁用死锁检测。
行级锁定的优势:
-
当不同会话访问不同行时,减少锁冲突。
-
回滚更少的更改。
-
可以长时间锁定单个行。
表级锁定
MySQL 对MyISAM、MEMORY和MERGE表使用表级锁定,每次只允许一个会话更新这些表。这种锁定级别使这些存储引擎更适合只读、读多或单用户应用程序。
这些存储引擎通过在查询开始时一次性请求所有所需的锁并始终以相同顺序锁定表来避免死锁。这种权衡是这种策略降低了并发性;其他想要修改表的会话必须等到当前数据更改语句完成。
表级锁定的优点:
-
需要相对较少的内存(行锁定需要每行或每组行锁定的内存)
-
在大部分表上使用时速度很快,因为只涉及一个锁。
-
如果经常在大部分数据上执行
GROUP BY操作或必须频繁扫描整个表,则速度很快。
MySQL 授予表写锁如下:
-
如果表上没有锁,请在其上放置写锁。
-
否则,将锁请求放入写锁队列。
MySQL 授予表读锁如下:
-
如果表上没有写锁,请在其上放置读锁。
-
否则,将锁请求放入读锁队列。
表更新比表检索具有更高的优先级。因此,当释放锁时,锁将提供给写锁队列中的请求,然后提供给读锁队列中的请求。这确保了即使表上有大量SELECT活动,对表的更新也不会“饿死”。但是,如果对表进行了许多更新,SELECT语句将等待直到没有更多更新。
有关修改读写优先级的信息,请参见第 10.11.2 节,“表锁问题”。
您可以通过检查Table_locks_immediate和Table_locks_waited状态变量来分析系统上的表锁争用情况,这些变量分别表示请求表锁可以立即授予的次数以及必须等待的次数:
mysql> SHOW STATUS LIKE 'Table%';
+-----------------------+---------+
| Variable_name | Value |
+-----------------------+---------+
| Table_locks_immediate | 1151552 |
| Table_locks_waited | 15324 |
+-----------------------+---------+
Performance Schema 锁表还提供了锁定信息。请参见第 29.12.13 节,“Performance Schema 锁表”。
MyISAM 存储引擎支持并发插入以减少对给定表的读取者和写入者之间的争用:如果 MyISAM 表在数据文件中间没有空闲块,则行始终插入到数据文件的末尾。在这种情况下,您可以在不锁定的情况下自由混合并发 INSERT 和 SELECT 语句用于 MyISAM 表。也就是说,您可以在其他客户端从中读取时向 MyISAM 表插入行。空洞可能是由于从表中间删除或更新行而导致的。如果有空洞,则并发插入将被禁用,但在所有空洞填满新数据后会自动重新启用。要控制此行为,请使用 concurrent_insert 系统变量。参见 Section 10.11.3, “Concurrent Inserts”。
如果您使用 LOCK TABLES 明确获取表锁,则可以请求 READ LOCAL 锁而不是 READ 锁,以便其他会话在您锁定表时执行并发插入。
当无法进行并发插入时,要在表 t1 上执行许多 INSERT 和 SELECT 操作,您可以将行插入临时表 temp_t1,然后使用临时表中的行更新真实表:
mysql> LOCK TABLES t1 WRITE, temp_t1 WRITE;
mysql> INSERT INTO t1 SELECT * FROM temp_t1;
mysql> DELETE FROM temp_t1;
mysql> UNLOCK TABLES;
选择锁定类型
通常,在以下情况下,表锁优于行级锁:
-
表的大多数语句是读取操作。
-
表的语句是读取和写入的混合,其中写入是更新或删除单行,可以通过一个键读取获取:
UPDATE *tbl_name* SET *column*=*value* WHERE *unique_key_col*=*key_value*; DELETE FROM *tbl_name* WHERE *unique_key_col*=*key_value*; -
SELECT与并发INSERT语句结合,以及很少的UPDATE或DELETE语句。 -
对整个表进行许多扫描或
GROUP BY操作,没有任何写入者。
使用更高级别的锁,您可以更容易地通过支持不同类型的锁来调整应用程序,因为与行级锁相比,锁开销更小。
除了行级锁之外的选项:
-
版本控制(例如 MySQL 中用于并发插入的方式),在同一时间可能有一个写入者和多个读取者。这意味着数据库或表支持根据访问开始时间而为数据提供不同视图。其他常见术语包括“时间旅行”,“写时复制”或“按需复制”。
-
在许多情况下,按需复制比行级锁更优越。然而,在最坏的情况下,它可能比使用普通锁占用更多内存。
-
你可以使用应用程序级别的锁,而不是使用行级别的锁,比如 MySQL 中提供的
GET_LOCK()和RELEASE_LOCK()。这些是咨询性锁,所以只能与相互合作的应用程序一起使用。参见第 14.14 节,“锁定函数”。
10.11.2 表锁定问题
InnoDB表使用行级锁定,以便多个会话和应用程序可以同时从同一表中读取和写入,而不必等待或产生不一致的结果。对于这种存储引擎,避免使用LOCK TABLES语句,因为它不提供任何额外的保护,而是降低并发性。自动的行级锁定使这些表适用于您最繁忙的数据库和最重要的数据,同时简化应用程序逻辑,因为您不需要锁定和解锁表。因此,InnoDB存储引擎是 MySQL 的默认存储引擎。
MySQL 对所有存储引擎使用表锁定(而不是页面、行或列锁定),除了InnoDB。锁定操作本身没有太多开销。但由于一次只能有一个会话写入表,对于这些其他存储引擎的最佳性能,主要用于经常查询但很少插入或更新的表。
-
支持 InnoDB 的性能考虑
-
锁定性能问题的解决方法
支持 InnoDB 的性能考虑
在选择是使用InnoDB还是其他存储引擎创建表时,请记住表锁定的以下缺点:
-
表锁定使许多会话可以同时从表中读取,但如果会话想要写入表,则必须首先获得独占访问,这意味着它可能必须等待其他会话先完成对表的操作。在更新期间,所有其他想要访问此特定表的会话必须等待更新完成。
-
当会话因磁盘已满而等待时,表锁定会导致问题,需要在会话继续之前释放空间。在这种情况下,所有想要访问问题表的会话也会进入等待状态,直到更多的磁盘空间可用。
-
运行时间较长的
SELECT语句会阻止其他会话在此期间更新表,使其他会话看起来缓慢或无响应。当会话等待独占访问表以进行更新时,发出SELECT语句的其他会话排队等待,即使对于只读会话也会降低并发性。
锁定性能问题的解决方法
以下项目描述了一些避免或减少由表锁定引起的争用的方法:
-
考虑将表切换到
InnoDB存储引擎,可以在设置期间使用CREATE TABLE ... ENGINE=INNODB,或者对现有表使用ALTER TABLE ... ENGINE=INNODB。有关此存储引擎的更多详细信息,请参见第十七章,InnoDB 存储引擎。 -
优化
SELECT语句以使其运行更快,从而使其锁定表的时间更短。您可能需要创建一些汇总表来实现这一点。 -
使用带有
--low-priority-updates启动mysqld。对于仅使用表级锁定的存储引擎(如MyISAM、MEMORY和MERGE),这会使所有更新(修改)表的语句比SELECT语句具有较低的优先级。在这种情况下,前述情景中的第二个SELECT语句将在UPDATE语句之���执行,并且不会等待第一个SELECT语句完成。 -
要指定在特定连接中发出的所有更新都应以低优先级执行,请将
low_priority_updates服务器系统变量设置为 1。 -
要给特定的
INSERT、UPDATE或DELETE语句降低优先级,请使用LOW_PRIORITY属性。 -
要给特定的
SELECT语句提高优先级,请使用HIGH_PRIORITY属性。请参见第 15.2.13 节,“SELECT 语句”。 -
使用较低值的
max_write_lock_count系统变量启动mysqld,以强制 MySQL 在特定数量的写锁定表之后(例如,对于插入操作)暂时提升所有等待表的SELECT语句的优先级。这允许在一定数量的写锁之后进行读锁定。 -
如果您在混合使用
SELECT和DELETE语句时遇到问题,DELETE的LIMIT选项可能会有所帮助。请参见第 15.2.2 节,“DELETE 语句”。 -
在
SELECT语句中使用SQL_BUFFER_RESULT可以帮助缩短表锁定的持续时间。请参见第 15.2.13 节,“SELECT 语句”。 -
将表内容拆分为单独的表可能会有所帮助,允许查询针对一个表中的列运行,而更新则限制在另一个表中的列。
-
您可以更改
mysys/thr_lock.c中的锁定代码以使用单个队列。在这种情况下,写锁和读锁将具有相同的优先级,这可能有助于某些应用程序。
10.11.3 并发插入
MyISAM 存储引擎支持并发插入,以减少读者和写者在给定表中的竞争:如果 MyISAM 表在数据文件中没有空洞(中间删除的行),则可以执行 INSERT 语句,同时向表的末尾添加行,而 SELECT 语句正在从表中读取行。如果有多个 INSERT 语句,则它们会被排队并按顺序执行,与 SELECT 语句并发执行。并发 INSERT 的结果可能不会立即可见。
concurrent_insert 系统变量可用于修改并发插入处理。默认情况下,该变量设置为 AUTO(或 1),并发插入会按照上述描述进行处理。如果 concurrent_insert 设置为 NEVER(或 0),则禁用并发插入。如果该变量设置为 ALWAYS(或 2),即使对于具有已删除行的表,也允许在表的末尾进行并发插入。另请参阅 concurrent_insert 系统变量的描述。
如果您正在使用二进制日志,对于 CREATE ... SELECT 或 INSERT ... SELECT 语句,将并发插入转换为普通插入。这样做是为了确保您可以通过在备份操作期间应用日志来重新创建表的精确副本。请参阅 Section 7.4.4, “The Binary Log”。此外,对于这些语句,会在所选表上放置读锁,以阻止对该表的插入。其效果是,该表的并发插入也必须等待。
使用 LOAD DATA,如果您在满足并发插入条件的 MyISAM 表中指定 CONCURRENT(即,它在中间不包含空闲块),则其他会话可以在 LOAD DATA 执行时从表中检索数据。即使没有其他会话同时使用该表,使用 CONCURRENT 选项也会稍微影响 LOAD DATA 的性能。
如果您指定 HIGH_PRIORITY,它会覆盖 --low-priority-updates 选项的效果,如果服务器是以该选项启动的。它还会导致不使用并发插入。
对于LOCK TABLE,READ LOCAL和READ之间的区别在于,READ LOCAL允许非冲突的INSERT语句(并发插入)在持有锁的情况下执行。然而,如果在持有锁的同时要使用外部进程来操作数据库,则不能使用此选项。
10.11.4 元数据锁定
MySQL 使用元数据锁定来管理对数据库对象的并发访问,并确保数据一致性。元数据锁定不仅适用于表,还适用于模式、存储程序(过程、函数、触发器、计划事件)、表空间、使用 GET_LOCK() 函数获取的用户锁(参见 Section 14.14, “锁定函数”),以及在 Section 7.6.9.1, “锁定服务” 中描述的锁定服务中获取的锁。
Performance Schema metadata_locks 表公开了元数据锁信息,可以用于查看哪些会话持有锁,正在等待锁等情况。详情请参见 Section 29.12.13.3, “metadata_locks 表”。
元数据锁定确实会带来一些开销,随着查询量的增加而增加。当多个查询尝试访问相同对象时,元数据争用会增加。
元数据锁定不是表定义缓存的替代品,其互斥体和锁与 LOCK_open 互斥体不同。以下讨论提供了有关元数据锁定工作原理的一些信息。
-
元数据锁获取
-
元数据锁释放
元数据锁获取
如果有多个等待者请求同一个锁,则最高优先级的锁请求首先得到满足,与 max_write_lock_count 系统变量相关的一个例外。写锁请求比读锁请求具有更高的优先级。但是,如果 max_write_lock_count 设置为较低值(比如,10),则读锁请求可能优先于待处理的写锁请求,如果读锁请求已经被忽略以优先处理 10 个写锁请求。通常情况下,这种行为不会发生,因为 max_write_lock_count 默认情况下具有非常大的值。
语句逐个获取元数据锁,而不是同时获取,并在此过程中执行死锁检测。
DML 语句通常按照语句中提到表的顺序获取锁。
DDL 语句、LOCK TABLES和其他类似语句尝试通过按名称顺序在显式命名的表上获取锁来减少并发 DDL 语句之间可能发生的死锁数量。对于隐式使用的表(例如外键关系中必须锁定的表),锁可能按不同顺序获取。
例如,RENAME TABLE是一个按名称顺序获取锁的 DDL 语句:
-
这个
RENAME TABLE语句将tbla重命名为其他内容,并将tblc重命名为tbla:RENAME TABLE tbla TO tbld, tblc TO tbla;该语句按顺序在
tbla、tblc和tbld上获取元数据锁(因为tbld在名称顺序中跟随tblc)。 -
这个稍有不同的语句还将
tbla重命名为其他内容,并将tblc重命名为tbla:RENAME TABLE tbla TO tblb, tblc TO tbla;在这种情况下,语句按顺序在
tbla、tblb和tblc上获取元数据锁(因为tblb在名称顺序中位于tblc之前)。
两个语句按顺序获取tbla和tblc上的锁,但在剩余表名的锁是在tblc之前还是之后获取有所不同。
当多个事务同时执行时,元数据锁获取顺序可能会影响操作结果,如下例所示。
从具有相同结构的两个表x和x_new开始。三个客户端发出涉及这些表的语句:
Client 1:
LOCK TABLE x WRITE, x_new WRITE;
该语句按名称顺序请求并获取x和x_new上的写锁。
Client 2:
INSERT INTO x VALUES(1);
该语句请求并在x上等待写锁。
Client 3:
RENAME TABLE x TO x_old, x_new TO x;
该语句按名称顺序请求x、x_new和x_old上的排他锁,但在等待x上的锁时被阻塞。
Client 1:
UNLOCK TABLES;
该语句释放了x和x_new上的写锁。Client 3 对x的排他锁请求比 Client 2 的写锁请求优先级更高,因此 Client 3 先获取了x的锁,然后也获取了x_new和x_old的锁,执行重命名操作,然后释放锁。然后 Client 2 获取了x的锁,执行插入操作,然后释放锁。
锁获取顺序导致RENAME TABLE在INSERT之前执行。插入发生的x是 Client 2 发出插入时命名为x_new,并由 Client 3 重命名为x的表:
mysql> SELECT * FROM x;
+------+
| i |
+------+
| 1 |
+------+
mysql> SELECT * FROM x_old;
Empty set (0.01 sec)
现在改为以名称为x和new_x的表具有相同结构开始。再次,三个客户端发出涉及这些表的语句:
Client 1:
LOCK TABLE x WRITE, new_x WRITE;
该语句按名称顺序请求并获取new_x和x上的写锁。
Client 2:
INSERT INTO x VALUES(1);
该语句请求并在x上等待写锁。
Client 3:
RENAME TABLE x TO old_x, new_x TO x;
该语句按名称顺序请求new_x、old_x和x上的排他锁,但在等待new_x上的锁时被阻塞。
Client 1:
UNLOCK TABLES;
该语句释放了x和new_x的写锁。对于x,唯一的挂起请求来自客户端 2,因此客户端 2 获取其锁,执行插入操作,并释放锁。对于new_x,唯一的挂起请求来自客户端 3,允许其获取该锁(以及old_x的锁)。重命名操作仍然会因为在客户端 2 插入完成并释放其锁之前对x的锁而阻塞。然后客户端 3 获取x的锁,执行重命名操作,并释放其锁。
在这种情况下,锁获取顺序导致INSERT在RENAME TABLE之前执行。插入发生的x是原始的x,现在被重命名为old_x:
mysql> SELECT * FROM x;
Empty set (0.01 sec)
mysql> SELECT * FROM old_x;
+------+
| i |
+------+
| 1 |
+------+
如果并发语句中锁获取顺序对操作结果有影响,如前面的例子,您可以调整表名以影响锁获取顺序。
为了防止相关表上的冲突 DML 和 DDL 操作同时执行,元数据锁会根据外键约束扩展到相关表。在更新父表时,会在更新外键元数据时在子表上获取元数据锁。外键元数据由子表拥有。
元数据锁释放
为确保事务的可串行化,服务器不得允许一个会话在另一个会话中的未完成的显式或隐式启动的事务中对表执行数据定义语言(DDL)语句。服务器通过在事务中使用的表上获取元数据锁并推迟释放这些锁直到事务结束来实现这一点。表上的元数据锁阻止对表结构的更改。这种锁定方法意味着一个会话中正在使用的表在事务结束之前不能被其他会话用于 DDL 语句。
这个原则不仅适用于事务表,也适用于非事务表。假设一个会话开始一个事务,使用事务表t和非事务表nt如下:
START TRANSACTION;
SELECT * FROM t;
SELECT * FROM nt;
服务器在事务结束之前会持有表t和nt的元数据锁。如果另一个会话尝试在任一表上执行 DDL 或写锁操作,它会阻塞,直到事务结束时释放元数据锁。例如,如果第二个会话尝试执行以下任何操作,它会被阻塞:
DROP TABLE t;
ALTER TABLE t ...;
DROP TABLE nt;
ALTER TABLE nt ...;
LOCK TABLE t ... WRITE;
相同的行为也适用于LOCK TABLES ... READ。也就是说,显式或隐式启动的更新任何表(事务或非事务)的事务会被LOCK TABLES ... READ阻塞,并且被该表阻塞。
如果服务器为一个在语法上有效但在执行过程中失败的语句获取元数据锁,它不会提前释放锁。锁的释放仍然延迟到事务结束,因为失败的语句被写入二进制日志,而锁保护日志的一致性。
在自动提交模式下,每个语句实际上是一个完整的事务,因此为语句获取的元数据锁仅在语句结束时保持。
在PREPARE语句期间获取的元数据锁在语句准备完成后会被释放,即使准备过程发生在多语句事务中。
截至 MySQL 8.0.13 版本,对于处于PREPARED状态的 XA 事务,元数据锁在客户端断开连接和服务器重新启动时会被保留,直到执行XA COMMIT或XA ROLLBACK。
10.11.5 外部锁定
外部锁定是通过文件系统锁定来管理多个进程对MyISAM数据库表的争用。在单个进程(如 MySQL 服务器)不能被假定为是唯一需要访问表的进程的情况下,会使用外部锁定。以下是一些示例:
-
如果您运行多个使用相同数据库目录(不推荐)的服务器,则每个服务器必须启用外部锁定。
-
如果您使用myisamchk对
MyISAM表执行表维护操作,则必须确保服务器未运行,或者服务器已启用外部锁定,以便根据需要锁定表文件,以便与myisamchk协调访问表。对于使用myisampack对MyISAM表进行打包也是如此。如果服务器启用了外部锁定,您可以随时使用myisamchk进行读取操作,例如检查表。在这种情况下,如果服务器尝试更新myisamchk正在使用的表,服务器会等待myisamchk完成后才继续。
如果您使用myisamchk进行写操作,如修复或优化表,或者使用myisampack对表进行打包,必须始终确保mysqld服务器未使用该表。如果不停止mysqld,至少在运行myisamchk之前执行mysqladmin flush-tables。如果服务器和myisamchk同时访问表,您的表可能会损坏。
在外部锁定生效时,每个需要访问表格的进程在继续访问表格之前会获取表格文件的文件系统锁定。如果无法获取所有必要的锁定,该进程将被阻止访问表格,直到可以获取锁定(在当前持有锁定的进程释放它们后)。
外部锁定会影响服务器性能,因为服务器有时必须等待其他进程才能访问表格。
如果你运行单个服务器来访问给定的数据目录(这是通常情况),并且没有其他程序需要在服务器运行时修改表格,那么外部锁定是不必要的。如果只是用其他程序读取表格,那么不需要外部锁定,尽管myisamchk在读取表格时,如果服务器更改表格,可能会报告警告。
禁用外部锁定后,要使用myisamchk,你必须在myisamchk执行时要么停止服务器,要么在运行myisamchk之前锁定和刷新表格。为避免此要求,使用CHECK TABLE和REPAIR TABLE语句来检查和修复MyISAM表格。
对于mysqld,外部锁定由skip_external_locking系统变量的值控制。当启用此变量时,外部锁定被禁用,反之亦然。外部锁定默认情况下被禁用。
可以通过在服务器启动时使用--external-locking或--skip-external-locking选项来控制外部锁定的使用。
如果使用外部锁定选项来允许多个 MySQL 进程更新MyISAM表格,请不要使用带有ALL设置的delay_key_write系统变量启动服务器,也不要为任何共享表格使用DELAY_KEY_WRITE=1表选项。否则,可能会导致索引损坏。
满足这个条件的最简单方法是始终将--external-locking与--delay-key-write=OFF一起使用。(默认情况下不这样做,因为在许多设置中,拥有前述选项的混合是有用的。)
10.12 优化 MySQL 服务器
10.12.1 优化磁盘 I/O
10.12.2 使用符号链接
10.12.3 优化内存使用
本节讨论了数据库服务器的优化技术,主要涉及系统配置而不是调整 SQL 语句。本节中的信息适用于希望确保管理的服务器性能和可伸缩性的数据库管理员;为构建包括设置数据库的安装脚本的开发人员;以及自行运行 MySQL 进行开发、测试等的人,他们希望最大化自己的生产力。
10.12.1 优化磁盘 I/O
本节描述了当你可以为数据库服务器提供更多和更快的存储硬件时如何配置存储设备。有关优化InnoDB配置以提高 I/O 性能的信息,请参见第 10.5.8 节,“优化 InnoDB 磁盘 I/O”。
-
磁盘寻道是一个巨大的性能瓶颈。当数据量变得如此之大以至于有效缓存变得不可能时,这个问题变得更加明显。对于访问数据比较随机的大型数据库,你可以确信你至少需要一个磁盘寻道来读取数据,以及几个磁盘寻道来写入数据。为了最小化这个问题,使用寻道时间较低的磁盘。
-
增加可用磁盘轴承的数量(从而减少寻道开销),可以通过将文件符号链接到不同的磁盘或者对磁盘进行条带化来实现:
-
使用符号链接
这意味着,对于
MyISAM表,你可以将索引文件和数据文件从它们在数据目录中的通常位置创建符号链接到另一个磁盘(也可以进行条带化)。假设该磁盘没有用于其他目的,这将使寻道和读取时间更好。参见第 10.12.2 节,“使用符号链接”。InnoDB表不支持使用符号链接。但是,可以将InnoDB数据和日志文件放在不同的物理磁盘上。更多信息,请参见第 10.5.8 节,“优化 InnoDB 磁盘 I/O”。 -
条带化
条带化意味着你有许多磁盘,并且将第一个块放在第一个磁盘上,第二个块放在第二个磁盘上,第*
N个块放在(N* MOD磁盘数量)磁盘上,依此类推。这意味着如果你的正常数据大小小于条带大小(或者完全对齐),你将获得更好的性能。条带化非常依赖于操作系统和条带大小,因此请使用不同的条带大小对你的应用程序进行基准测试。参见第 10.13.2 节,“使用自己的基准测试”。条带化的速度差异非常依赖于参数。根据你设置的条带化参数和磁盘数量,你可能会得到数量级差异的结果。你必须选择优化随机访问还是顺序访问。
-
-
为了可靠性,你可能希望使用 RAID 0+1(条带化加镜像),但在这种情况下,你需要 2×*
N个驱动器来存储N*个驱动器的数据。如果你有资金,这可能是最好的选��。然而,你可能还需要投资一些卷管理软件来有效处理它。 -
根据数据类型的重要性,可以根据 RAID 级别进行调整。例如,将可以重新生成的半重要数据存储在 RAID 0 磁盘上,但将主机信息和日志等真正重要的数据存储在 RAID 0+1 或 RAID
N磁盘上。如果有许多写操作,RAIDN可能会成为问题,因为更新奇偶校验位需要时间。 -
您还可以设置数据库使用的文件系统的参数:
如果您不需要知道文件上次访问的时间(这在数据库服务器上并不真正有用),您可以使用
-o noatime选项挂载文件系统。这样可以跳过对文件系统中 inode 上的最后访问时间的更新,从而避免一些磁盘寻道。在许多操作系统上,您可以通过使用
-o async选项挂载文件系统来异步更新文件系统。如果您的计算机相当稳定,这应该可以在不牺牲太多可靠性的情况下提供更好的性能。(在 Linux 上,默认情况下启用此标志。)
使用 NFS 与 MySQL
在考虑是否与 MySQL 一起使用 NFS 时应谨慎。潜在问题因操作系统和 NFS 版本而异,包括以下内容:
-
放置在 NFS 卷上的 MySQL 数据和日志文件被锁定并无法使用。在多个 MySQL 实例访问相同数据目录或 MySQL 因不当关闭(例如由于断电)而发生锁定问题的情况下可能会发生。NFS 版本 4 通过引入咨询和基于租约的锁定来解决潜在的锁定问题。然而,不建议在多个 MySQL 实例之间共享数据目录。
-
由于消息接收顺序混乱或网络流量丢失而引入的数据不一致性。为避免此问题,请使用带有
hard和intr挂载选项的 TCP。 -
最大文件大小限制。NFS 版本 2 客户端只能访问文件的最低 2GB(带符号 32 位偏移)。NFS 版本 3 客户端支持更大的文件(最多 64 位偏移)。支持的最大文件大小还取决于 NFS 服务器的本地文件系统。
在专业 SAN 环境或其他存储系统中使用 NFS 往往比在此类环境之外使用 NFS 提供更高的可靠性。然而,在 SAN 环境中使用 NFS 可能比直接连接或总线连接的非旋转存储慢。
如果选择使用 NFS,建议使用 NFS 版本 4 或更高版本,并在部署到生产环境之前彻底测试您的 NFS 设置。
10.12.2 使用符号链接
10.12.2.1 在 Unix 上使用符号链接处理数据库
10.12.2.2 在 Unix 上使用符号链接处理 MyISAM 表
10.12.2.3 在 Windows 上使用符号链接处理数据库
您可以将数据库或表从数据库目录移动到其他位置,并用指向新位置的符号链接替换它们。例如,您可能想要这样做,将数据库移动到具有更多可用空间的文件系统,或者通过将表分布到不同的磁盘上来提高系统速度。
对于InnoDB表,使用CREATE TABLE语句的DATA DIRECTORY子句,而不是符号链接,如 Section 17.6.1.2,“外部创建表”中所解释的那样。这个新功能是一种受支持的跨平台技术。
推荐的方法是将整个数据库目录创建符号链接到不同的磁盘上。只有在万不得已的情况下才将MyISAM表创建符号链接。
要确定数据目录的位置,请使用以下语句:
SHOW VARIABLES LIKE 'datadir';
原文:
dev.mysql.com/doc/refman/8.0/en/symbolic-links-to-databases.html
10.12.2.1 在 Unix 上使用符号链接进行数据库操作
在 Unix 上,可以按照以下步骤使用符号链接来创建数据库:
-
使用
CREATE DATABASE创建数据库:mysql> CREATE DATABASE mydb1;使用
CREATE DATABASE在 MySQL 数据目录中创建数据库,并允许服务器更新数据字典中有关数据库目录的信息。 -
停止服务器以确保在移动数据库时不会发生任何活动。
-
将数据库目录移动到某个具有可用空间的磁盘上。例如,使用tar或mv。如果使用复制而不是移动数据库目录的方法,请在复制后删除原始数据库目录。
-
在数据目录中创建一个软链接,指向已移动的数据库目录。
$> ln -s */path/to/mydb1* */path/to/datadir*该命令在数据目录中创建一个名为
mydb1的符号链接。 -
重新启动服务器。
原文:
dev.mysql.com/doc/refman/8.0/en/symbolic-links-to-tables.html
10.12.2.2 在 Unix 上使用符号链接的 MyISAM 表
注意
此处描述的符号链接支持,以及控制它的--symbolic-links选项已被弃用;预计在未来的 MySQL 版本中将删除这些内容。此外,默认情况下该选项已禁用。
仅对 MyISAM 表完全支持符号链接。对于其他存储引擎表使用的文件,如果尝试使用符号链接可能会出现奇怪的问题。对于 InnoDB 表,请使用 Section 17.6.1.2, “Creating Tables Externally”中解释的替代技术。
在没有完全运行realpath()调用的系统上不要对表进行符号链接。 (Linux 和 Solaris 支持realpath()). 要确定您的系统是否支持符号链接,请使用以下语句检查have_symlink系统变量的值:
SHOW VARIABLES LIKE 'have_symlink';
以下是 MyISAM 表的符号链接处理方式:
-
在数据目录中,您始终有数据(
.MYD)文件和索引(.MYI)文件。数据文件和索引文件可以移动到其他位置,并在数据目录中用符号链接替换。 -
你可以将数据文件和索引文件分别链接到不同的目录。
-
要指示正在运行的 MySQL 服务器执行符号链接操作,请使用
CREATE TABLE中的DATA DIRECTORY和INDEX DIRECTORY选项。参见 Section 15.1.20, “CREATE TABLE Statement”。或者,如果mysqld没有运行,可以使用命令行中的ln -s手动完成符号链接。注意
与
DATA DIRECTORY和INDEX DIRECTORY选项中的路径可能不包括 MySQLdata目录。 (Bug #32167) -
myisamchk不会用数据文件或索引文件替换符号链接。它直接在符号链接指向的文件上工作。任何临时文件都将在数据文件或索引文件所在的目录中创建。对于
ALTER TABLE、OPTIMIZE TABLE和REPAIR TABLE语句也是如此。 -
注意
当您删除一个使用符号链接的表时,符号链接和符号链接指向的文件都会被删除。这是一个极好的理由不以
root操作系统用户身份运行mysqld或允许操作系统用户对 MySQL 数据库目录具有写访问权限。 -
如果您使用
ALTER TABLE ... RENAME或RENAME TABLE重命名一个表,并且不将表移动到另一个数据库,则数据库目录中的符号链接将被重命名为新名称,并相应地重命名数据文件和索引文件。 -
如果您使用
ALTER TABLE ... RENAME或RENAME TABLE将表移动到另一个数据库,表将移动到另一个数据库目录。如果表名发生更改,则新数据库目录中的符号链接将被重命名为新名称,并相应地重命名数据文件和索引文件。 -
如果您不使用符号链接,请使用
--skip-symbolic-links选项启动mysqld,以确保没有人可以使用mysqld删除或重命名数据目录之外的文件。
这些表符号链接操作不受支持:
ALTER TABLE会忽略DATA DIRECTORY和INDEX DIRECTORY表选项。
原文:
dev.mysql.com/doc/refman/8.0/en/windows-symbolic-links.html
10.12.2.3 在 Windows 上使用符号链接进行数据库操作
在 Windows 上,可以使用符号链接来创建数据库目录。这使您可以通过设置符号链接将数据库目录放在不同的位置(例如,不同的磁盘)上。在 Windows 上使用数据库符号链接类似于在 Unix 上的使用,尽管设置链接的过程有所不同。
假设您想要将名为 mydb 的数据库的数据库目录放在 D:\data\mydb。为此,在 MySQL 数据目录中创建一个指向 D:\data\mydb 的符号链接。但是,在创建符号链接之前,请确保 D:\data\mydb 目录存在,必要时进行创建。如果您已经在数据目录中有一个名为 mydb 的数据库目录,请将其移动到 D:\data。否则,符号链接将不起作用。为避免问题,请确保在移动数据库目录时服务器未运行。
在 Windows 上,您可以使用 mklink 命令创建符号链接。此命令需要管理员权限。
-
确保所需的数据库路径存在。在本示例中,我们使用
D:\data\mydb和一个名为mydb的数据库。 -
如果数据库尚不存在,请在 mysql 客户端中发出
CREATE DATABASE mydb来创建它。 -
停止 MySQL 服务。
-
使用 Windows 资源管理器或命令行,将数据目录中的
mydb目录移动到D:\data,替换同名目录。 -
如果您尚未使用命令提示符,请打开它,并将位置更改为数据目录,如下所示:
C:\> cd *\path\to\datadir*如果您的 MySQL 安装在默认位置,您可以使用以下命令:
C:\> cd C:\ProgramData\MySQL\MySQL Server 8.0\Data -
在数据目录中,创建一个名为
mydb的符号链接,指向数据库目录的位置:C:\> mklink /d mydb D:\data\mydb -
启动 MySQL 服务。
之后,所有在数据库 mydb 中创建的表都将在 D:\data\mydb 中创建。
或者,在 MySQL 支持的任何 Windows 版本上,您可以通过在数据目录中创建一个包含指向目标目录路径的 .sym 文件来创建 MySQL 数据库的符号链接。该文件应命名为 *db_name*.sym,其中 db_name 是数据库名称。
Windows 上默认启用使用 .sym 文件创建数据库符号链接的支持。如果您不需要 .sym 文件符号链接,可以通过使用 --skip-symbolic-links 选项启动 mysqld 来禁用对它们的支持。要确定您的系统是否支持 .sym 文件符号链接,请使用以下语句检查 have_symlink 系统变量的值:
SHOW VARIABLES LIKE 'have_symlink';
要创建 .sym 文件符号链接,请按照以下步骤进行:
-
将位置更改为数据目录:
C:\> cd *\path\to\datadir* -
在数据目录中,创建一个名为
mydb.sym的文本文件,其中包含此路径名:D:\data\mydb\注意
新数据库和表的路径名应为绝对路径。如果您指定相对路径,则位置相对于
mydb.sym文件。
在此之后,所有在数据库mydb中创建的表都将被创建在D:\data\mydb中。
10.12.3 优化内存使用
MySQL 如何使用内存
监控 MySQL 内存使用
启用大页支持
10.12.3.1 MySQL 如何使用内存
MySQL 分配缓冲区和缓存以提高数据库操作的性能。默认配置旨在允许 MySQL 服务器在具有大约 512MB RAM 的虚拟机上启动。您可以通过增加某些与缓存和缓冲区相关的系统变量的值来提高 MySQL 的性能。您还可以修改默认配置以在内存有限的系统上运行 MySQL。
以下列表描述了 MySQL 使用内存的一些方式。在适用的情况下,引用了相关的系统变量。一些项目是存储引擎或特定功能。
-
InnoDB缓冲池是一个保存用于表、索引和其他辅助缓冲区的缓存InnoDB数据的内存区域。为了提高高容量读操作的效率,缓冲池被划分为页,这些页可能可以容纳多行。为了提高缓存管理的效率,缓冲池被实现为页的链表;很少使用的数据会根据 Lru 算法的变体从缓存中淘汰。更多信息,请参见第 17.5.1 节,“缓冲池”。缓冲池的大小对系统性能很重要:
-
InnoDB在服务器启动时为整个缓冲池分配内存,使用malloc()操作。innodb_buffer_pool_size系统变量定义了缓冲池的大小。通常,推荐的innodb_buffer_pool_size值为系统内存的 50 到 75%。innodb_buffer_pool_size可以在服务器运行时动态配置。更多信息,请参见第 17.8.3.1 节,“配置 InnoDB 缓冲池大小”。 -
在具有大量内存的系统上,可以通过将缓冲池划分为多个缓冲池实例来提高并发性。
innodb_buffer_pool_instances系统变量定义了缓冲池实例的数量。 -
过小的缓冲池可能会导致过多的翻转,因为页面从缓冲池刷新出来,只是在短时间后再次需要。
-
过大的缓冲池可能会导致由于内存竞争而发生交换。
-
-
存储引擎接口使优化器能够提供关于要用于估计可能读取多行的扫描的记录缓冲区大小的信息。缓冲区大小可以根据估计的大小变化。
InnoDB使用这种可变大小的缓冲能力来利用行预取,并减少锁定和 B 树导航的开销。 -
所有线程共享
MyISAM关键缓冲区。key_buffer_size系统变量确定其大小。对于每个
MyISAM表,服务器打开一次索引文件;对于每个同时运行访问该表的线程,数据文件会被打开一次。对于每个并发线程,都会分配一个表结构,每个列的列结构,以及大小为3 * *N*的缓冲区(其中*N*是最大行长度,不包括BLOB列)。一个BLOB列需要五到八个字节加上BLOB数据的长度。MyISAM存储引擎为内部使用维护了一个额外的行缓冲区。 -
myisam_use_mmap系统变量可以设置为 1,以启用所有MyISAM表的内存映射。 -
如果一个内部内存临时表变得太大(根据
tmp_table_size和max_heap_table_size系统变量确定),MySQL 会自动将表从内存转换为磁盘格式。从 MySQL 8.0.16 开始,磁盘临时表总是使用InnoDB存储引擎。(以前,用于此目的的存储引擎是由internal_tmp_disk_storage_engine系统变量确定的,该变量不再受支持。)您可以按照第 10.4.4 节,“MySQL 中的内部临时表使用”中描述的方式增加临时表的大小。对于使用
CREATE TABLE显式创建的MEMORY表,只有max_heap_table_size系统变量确定表可以增长多大,而且不会转换为磁盘格式。 -
MySQL 性能模式是一个用于监视 MySQL 服务器在低级别执行的功能。性能模式动态地逐步分配内存,根据实际服务器负载来调整内存使用,而不是在服务器启动期间分配所需内存。一旦分配了内存,直到服务器重新启动之前都不会释放。更多信息,请参阅第 29.17 节,“性能模式内存分配模型”。
-
服务器用于管理客户端连接的每个线程都需要一些特定于线程的空间。以下列表指示了这些空间以及控制它们大小的系统变量:
-
一个堆栈(
thread_stack) -
一个连接缓冲区(
net_buffer_length) -
一个结果缓冲区(
net_buffer_length)
连接缓冲区和结果缓冲区的初始大小均为
net_buffer_length字节,但根据需要动态扩大至max_allowed_packet字节。每个 SQL 语句执行完毕后,结果缓冲区会缩小至net_buffer_length字节。在语句运行时,当前语句字符串的副本也会被分配。每个连接线程使用内存来计算语句摘要。服务器为每个会话分配
max_digest_length字节。请参阅第 29.10 节,“性能模式语句摘要和采样”。 -
-
所有线程共享相同的基本内存。
-
当不再需要一个线程时,分配给它的内存会被释放并返回给系统,除非线程重新进入线程缓存。在这种情况下,内存仍然保持分配状态。
-
每个执行表的顺序扫描的请求都会分配一个读取缓冲区。
read_buffer_size系统变量确定了缓冲区的大小。 -
在以任意顺序读取行(例如,遵循排序)时,可能会分配一个随机读取缓冲区以避免磁盘查找。
read_rnd_buffer_size系统变量确定了���冲区的大小。 -
所有连接都在单次执行中执行,大多数连接甚至可以在不使用临时表的情况下完成。大多数临时表都是基于内存的哈希表。具有大行长度(计算为所有列长度之和)或包含
BLOB列的临时表存储在磁盘上。 -
大多数执行排序操作的请求会分配一个排序缓冲区,并根据结果集大小分配零到两个临时文件。请参阅 Section B.3.3.5, “Where MySQL Stores Temporary Files”。
-
几乎所有的解析和计算都是在线程本地和可重用的内存池中完成的。对于小项目,不需要额外的内存开销,从而避免了常规的缓慢内存分配和释放。只有对于异常大的字符串才会分配内存。
-
对于每个具有
BLOB列的表,缓冲区会动态扩大以读取更大的BLOB值。如果扫描一个表,缓冲区会增长到最大的BLOB值的大小。 -
MySQL 需要为表缓存分配内存和描述符。所有正在使用的表的处理程序结构保存在表缓存中,并按照“先进先出”(FIFO)的方式进行管理。
table_open_cache系统变量定义了初始表缓存大小;请参阅 Section 10.4.3.1, “How MySQL Opens and Closes Tables”。MySQL 还需要为表定义缓存分配内存。
table_definition_cache系统变量定义了可以存储在表定义缓存中的表定义数量。如果使用大量表,可以创建一个大的表定义缓存以加快表的打开速度。表定义缓存占用的空间较小,不使用文件描述符,与表缓存不同。 -
一个
FLUSH TABLES语句或mysqladmin flush-tables命令会立即关闭所有未使用的表,并标记所有正在使用的表在当前执行线程完成时关闭。这有效地释放了大部分正在使用的内存。FLUSH TABLES在所有表关闭后才会返回。 -
服务器会因为
GRANT、CREATE USER、CREATE SERVER和INSTALL PLUGIN语句而在内存中缓存信息。这些内存不会被相应的REVOKE、DROP USER、DROP SERVER和UNINSTALL PLUGIN语句释放,因此对于执行许多导致缓存的语句实例的服务器,除非使用FLUSH PRIVILEGES释放,否则缓存内存使用量会增加。 -
在复制拓扑中,以下设置会影响内存使用,并可以根据需要进行调整:
-
复制源上的
max_allowed_packet系统变量限制源发送给其复制品进行处理的最大消息大小。此设置默认为 64M。 -
多线程复制品上的
replica_pending_jobs_size_max(从 MySQL 8.0.26 开始)或 MySQL 8.0.26 之前的slave_pending_jobs_size_max系统变量设置用于保存等待处理消息的最大内存量。此设置默认为 128M。只有在需要时才分配内存,但如果您的复制拓扑有时处理大事务,则可能会使用它。这是一个软限制,可以处理更大的事务。 -
复制源或复制品上的
rpl_read_size系统变量控制从二进制日志文件和中继日志文件中读取的最小数据量(以字节为单位)。默认值为 8192 字节。为从二进制日志和中继日志文件中读取数据的每个线程分配了与此值大小相同的缓冲区,包括源上的转储线程和复制品上的协调器线程。 -
binlog_transaction_dependency_history_size系统变量限制保存在内存中的行哈希数量。 -
max_binlog_cache_size系统变量指定单个事务的内存使用上限。 -
max_binlog_stmt_cache_size系统变量指定语句缓存的内存使用上限。
-
ps 和其他系统状态程序可能会报告说mysqld使用了大量内存。这可能是由于不同内存地址上的线程堆栈导致的。例如,Solaris 版本的 ps 将堆栈之间未使用的内存计算为已使用内存。要验证这一点,请使用 swap -s 检查可用交换空间。我们使用了几种内存泄漏检测工具来测试mysqld(包括商业和开源的),因此不应该有内存泄漏。
原文:
dev.mysql.com/doc/refman/8.0/en/monitor-mysql-memory-use.html
10.12.3.2 监控 MySQL 内存使用情况
以下示例演示了如何使用性能模式和 sys 模式来监控 MySQL 内存使用情况。
大多数性能模式内存工具默认处于禁用状态。可以通过更新性能模式setup_instruments表的ENABLED列来启用工具。内存工具的名称形式为memory/*code_area*/*instrument_name*,其中*code_area是诸如sql或innodb之类的值,instrument_name*是工具的详细信息。
-
要查看可用的 MySQL 内存工具,请查询性能模式
setup_instruments表。以下查询返回所有代码区域的数百个内存工具。mysql> SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE '%memory%';通过指定代码区域,您可以缩小结果范围。例如,您可以通过指定
innodb作为代码区域来限制结果为InnoDB内存工具。mysql> SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE '%memory/innodb%'; +-------------------------------------------+---------+-------+ | NAME | ENABLED | TIMED | +-------------------------------------------+---------+-------+ | memory/innodb/adaptive hash index | NO | NO | | memory/innodb/buf_buf_pool | NO | NO | | memory/innodb/dict_stats_bg_recalc_pool_t | NO | NO | | memory/innodb/dict_stats_index_map_t | NO | NO | | memory/innodb/dict_stats_n_diff_on_level | NO | NO | | memory/innodb/other | NO | NO | | memory/innodb/row_log_buf | NO | NO | | memory/innodb/row_merge_sort | NO | NO | | memory/innodb/std | NO | NO | | memory/innodb/trx_sys_t::rw_trx_ids | NO | NO | ...根据您的 MySQL 安装,代码区域可能包括
performance_schema、sql、client、innodb、myisam、csv、memory、blackhole、archive、partition等。 -
要启用内存工具,请向您的 MySQL 配置文件添加
performance-schema-instrument规则。例如,要启用所有内存工具,请将此规则添加到您的配置文件中并重新启动服务器:performance-schema-instrument='memory/%=COUNTED'注意
在启动时启用内存工具可确保计算启动时发生的内存分配。
重新启动服务器后,性能模式
setup_instruments表的ENABLED列应报告您启用的内存工具为YES。性能模式setup_instruments表中的TIMED列对于内存工具是被忽略的,因为内存操作不计时。mysql> SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE '%memory/innodb%'; +-------------------------------------------+---------+-------+ | NAME | ENABLED | TIMED | +-------------------------------------------+---------+-------+ | memory/innodb/adaptive hash index | NO | NO | | memory/innodb/buf_buf_pool | NO | NO | | memory/innodb/dict_stats_bg_recalc_pool_t | NO | NO | | memory/innodb/dict_stats_index_map_t | NO | NO | | memory/innodb/dict_stats_n_diff_on_level | NO | NO | | memory/innodb/other | NO | NO | | memory/innodb/row_log_buf | NO | NO | | memory/innodb/row_merge_sort | NO | NO | | memory/innodb/std | NO | NO | | memory/innodb/trx_sys_t::rw_trx_ids | NO | NO | ... -
查询内存工具数据。在此示例中,内存工具数据在性能模式
memory_summary_global_by_event_name表中查询,该表按EVENT_NAME汇总数据。EVENT_NAME是工具的名称。以下查询返回
InnoDB缓冲池的内存数据。有关列描述,请参阅第 29.12.20.10 节,“内存摘要表”。mysql> SELECT * FROM performance_schema.memory_summary_global_by_event_name WHERE EVENT_NAME LIKE 'memory/innodb/buf_buf_pool'\G EVENT_NAME: memory/innodb/buf_buf_pool COUNT_ALLOC: 1 COUNT_FREE: 0 SUM_NUMBER_OF_BYTES_ALLOC: 137428992 SUM_NUMBER_OF_BYTES_FREE: 0 LOW_COUNT_USED: 0 CURRENT_COUNT_USED: 1 HIGH_COUNT_USED: 1 LOW_NUMBER_OF_BYTES_USED: 0 CURRENT_NUMBER_OF_BYTES_USED: 137428992 HIGH_NUMBER_OF_BYTES_USED: 137428992可以使用
sysschema 的memory_global_by_current_bytes表查询相同的基础数据,该表显示了服务器全局范围内当前内存使用情况,按分配类型进行了拆分。mysql> SELECT * FROM sys.memory_global_by_current_bytes WHERE event_name LIKE 'memory/innodb/buf_buf_pool'\G *************************** 1\. row *************************** event_name: memory/innodb/buf_buf_pool current_count: 1 current_alloc: 131.06 MiB current_avg_alloc: 131.06 MiB high_count: 1 high_alloc: 131.06 MiB high_avg_alloc: 131.06 MiB此
sysschema 查询通过代码区域对当前分配的内存(current_alloc)进行聚合:mysql> SELECT SUBSTRING_INDEX(event_name,'/',2) AS code_area, FORMAT_BYTES(SUM(current_alloc)) AS current_alloc FROM sys.x$memory_global_by_current_bytes GROUP BY SUBSTRING_INDEX(event_name,'/',2) ORDER BY SUM(current_alloc) DESC; +---------------------------+---------------+ | code_area | current_alloc | +---------------------------+---------------+ | memory/innodb | 843.24 MiB | | memory/performance_schema | 81.29 MiB | | memory/mysys | 8.20 MiB | | memory/sql | 2.47 MiB | | memory/memory | 174.01 KiB | | memory/myisam | 46.53 KiB | | memory/blackhole | 512 bytes | | memory/federated | 512 bytes | | memory/csv | 512 bytes | | memory/vio | 496 bytes | +---------------------------+---------------+注意
在 MySQL 8.0.16 之前,使用
sys.format_bytes()来进行FORMAT_BYTES()。有关
sysschema 的更多信息,请参阅 第三十章,MySQL sys Schema。
10.12.3.3 启用大页支持
一些硬件和操作系统架构支持大于默认值(通常为 4KB)的内存页。此支持的实际实现取决于底层硬件和操作系统。执行大量内存访问的应用程序可能通过使用大页获得性能改进,因为减少了 TLB(Translation Lookaside Buffer)缺失。
在 MySQL 中,大页可以被InnoDB使用,为其缓冲池和额外内存池分配内存。
MySQL 中标准使用大页尝试使用支持的最大大小,最高可达 4MB。在 Solaris 下,“超大页”功能使得可以使用高达 256MB 的页面。这个功能适用于最近的 SPARC 平台。可以通过使用--super-large-pages或--skip-super-large-pages选项来启用或禁用它。
MySQL 还支持 Linux 中的大页支持实现(在 Linux 中称为 HugeTLB)。
在 Linux 上使用大页之前,必须启用内核以支持它们,并且需要配置 HugeTLB 内存池。有关参考,HugeTBL API 在您的 Linux 源代码的Documentation/vm/hugetlbpage.txt文件中有文档。
一些最近的系统(如 Red Hat Enterprise Linux)的内核可能默认启用了大页功能。要检查您的内核是否为真,请使用以下命令,并查找包含“huge”的输出行:
$> grep -i huge /proc/meminfo
AnonHugePages: 2658304 kB
ShmemHugePages: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 0 kB
非空的命令输出表示大页支持存在,但零值表示未配置任何页面供使用。
如果您的内核需要重新配置以支持大页,请参考hugetlbpage.txt文件中的说明。
假设您的 Linux 内核已启用大页支持,请按以下步骤配置 MySQL 以使用它:
-
确定所需的大页数。这是 InnoDB 缓冲池大小除以大页大小,我们可以计算为
innodb_buffer_pool_size/Hugepagesize。假设innodb_buffer_pool_size的默认值(128MB)并使用从/proc/meminfo中获得的Hugepagesize值(2MB),这是 128MB / 2MB,或 64 个大页。我们称这个值为*P*。 -
作为系统根用户,在文本编辑器中打开文件
/etc/sysctl.conf,并添加此处显示的行,其中*P*是先前步骤中获得的大页数:vm.nr_hugepages=*P*使用先前获得的实际值,附加行应如下所示:
vm.nr_huge_pages=64保存更新后的文件。
-
作为系统根用户,运行以下命令:
$> sudo sysctl -p注意
在某些系统上,大页文件的名称可能略有不同;例如,某些发行版将其称为
nr_hugepages。如果sysctl返回与文件名相关的错误,请检查/proc/sys/vm中相应文件的名称,并使用该名称。要验证大页配置,请再次检查
/proc/meminfo,如前所述。现在,您应该在输出中看到一些额外的非零值,类似于这样:$> grep -i huge /proc/meminfo AnonHugePages: 2686976 kB ShmemHugePages: 0 kB HugePages_Total: 233 HugePages_Free: 233 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 477184 kB -
可选地,您可能希望压缩 Linux VM。您可以使用一系列命令来执行此操作,可能是在脚本文件中,类似于以下所示:
sync sync sync echo 3 > /proc/sys/vm/drop_caches echo 1 > /proc/sys/vm/compact_memory查看您的操作平台文档以获取有关如何执行此操作的更多信息。
-
检查服务器使用的任何配置文件,如
my.cnf,并确保innodb_buffer_pool_chunk_size设置为大于大页大小。此变量的默认值为 128M。 -
MySQL 服务器中默认情况下禁用大页支持。要启用它,请使用
--large-pages启动服务器。您还可以通过将以下行添加到服务器my.cnf文件的[mysqld]部分来执行此操作:large-pages=ON启用此选项后,
InnoDB会自动为其缓冲池和额外内存池使用大页。如果InnoDB无法执行此操作,则会回退到使用传统内存,并在错误日志中写入警告:警告:使用传统内存池。
您可以通过在重新启动mysqld后再次检查/proc/meminfo来验证 MySQL 是否正在使用大页,就像这样:
$> grep -i huge /proc/meminfo
AnonHugePages: 2516992 kB
ShmemHugePages: 0 kB
HugePages_Total: 233
HugePages_Free: 222
HugePages_Rsvd: 55
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 477184 kB
10.13 性能测量(基准测试)
原文:
dev.mysql.com/doc/refman/8.0/en/optimize-benchmarking.html
10.13.1 测量表达式和函数的速度
10.13.2 使用自定义基准
10.13.3 使用 performance_schema 测量性能
为了衡量性能,请考虑以下因素:
-
无论您是在安静系统上测量单个操作的速度,还是在一段时间内测试一组操作(“工作负载”)的工作方式。通过简单测试,通常测试改变一个方面(配置设置、表上的索引集合、查询中的 SQL 子句)如何影响性能。基准测试通常是长时间运行和复杂的性能测试,结果可能决定高级选择,如硬件和存储配置,或者何时升级到新的 MySQL 版本。
-
对于基准测试,有时必须模拟繁重的数据库工作负载以获得准确的图片。
-
性能可能会因为许多不同因素而变化,几个百分点的差异可能不是决定性的胜利。当在不同环境中进行测试时,结果可能会相反。
-
某些 MySQL 功能根据工作负载是否有助于性能。为了全面性,始终测试开启和关闭这些功能的性能。对于每种工作负载尝试的最重要功能是
InnoDB表的自适应哈希索引。
本节从单个开发人员可以执行的简单直接的测量技术开始,逐渐发展到需要额外专业知识来执行和解释结果的更复杂的技术。
10.13.1 测量表达式和函数的速度
要测量特定 MySQL 表达式或函数的速度,请使用 mysql 客户端程序调用 BENCHMARK() 函数。其语法为 BENCHMARK(*loop_count*,*expr*)。返回值始终为零,但 mysql 会打印一行显示语句执行大约花费的时间。例如:
mysql> SELECT BENCHMARK(1000000,1+1);
+------------------------+
| BENCHMARK(1000000,1+1) |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.32 sec)
这个结果是在一台 Pentium II 400MHz 系统上获得的。它显示 MySQL 在该系统上可以在 0.32 秒内执行 1,000,000 个简单的加法表达式。
内置的 MySQL 函数通常经过高度优化,但也可能有一些例外。BENCHMARK() 是一个很好的工具,可以找出某个函数是否影响了你的查询。
10.13.2 使用您自己的基准测试
对应用程序和数据库进行基准测试,找出瓶颈所在。在解决一个瓶颈(或用“虚拟”模块替换它)之后,您可以继续识别下一个瓶颈。即使您当前的应用程序整体性能是可接受的,您也应该至少为每个瓶颈制定计划,并决定如何解决它,以防有一天您真的需要额外的性能。
一个免费的基准测试套件是开源数据库基准测试,可在osdb.sourceforge.net/找到。
当系统负载非常重时,问题只会发生是非常常见的。我们曾经有许多客户在生产中遇到负载问题时联系我们。在大多数情况下,性能问题往往是由于基本数据库设计问题(例如,在高负载下表扫描效果不佳)或操作系统或库的问题。如果系统尚未投入生产,这些问题大多数情况下会更容易解决。
为避免出现此类问题,请在最坏的负载情况下对整个应用程序进行基准测试:
-
mysqlslap程序对于模拟多个客户端同时发出查询产生的高负载非常有帮助。请参阅 Section 6.5.8, “mysqlslap — A Load Emulation Client”。
-
您还可以尝试诸如 SysBench 和 DBT2 等基准测试软件包,可在
launchpad.net/sysbench和osdldbt.sourceforge.net/#dbt2找到。
这些程序或软件包可能会使系统崩溃,因此请确保仅在开发系统上使用它们。
10.13.3 使用 performance_schema 测量性能
原文:
dev.mysql.com/doc/refman/8.0/en/monitoring-performance-schema.html
你可以查询performance_schema数据库中的表,查看关于服务器性能特征和正在运行的应用程序的实时信息。详细信息请参见第二十九章,MySQL 性能模式。
10.14 检查服务器线程(进程)信息
10.14.1 访问进程列表
10.14.2 线程命令值
10.14.3 通用线程状态
10.14.4 复制源线程状态
10.14.5 复制 I/O(接收器)线程状态
10.14.6 复制 SQL 线程状态
10.14.7 复制连接线程状态
10.14.8 NDB 集群线程状态
10.14.9 事件调度器线程状态
要了解你的 MySQL 服务器正在做什么,检查进程列表可能会有所帮助,该列表显示了服务器内执行的一组线程当前正在执行的操作。例如:
mysql> SHOW PROCESSLIST\G
*************************** 1\. row ***************************
Id: 5
User: event_scheduler
Host: localhost
db: NULL
Command: Daemon
Time: 2756681
State: Waiting on empty queue
Info: NULL
*************************** 2\. row ***************************
Id: 20
User: me
Host: localhost:52943
db: test
Command: Query
Time: 0
State: starting
Info: SHOW PROCESSLIST
可以使用 KILL 语句终止线程。参见 第 15.7.8.4 节,“KILL 语句”。
10.14.1 访问进程列表
以下讨论列举了进程信息的来源,查看进程信息所需的权限,并描述了进程列表条目的内容。
-
进程信息来源
-
访问进程列表所需的权限
-
进程列表条目内容
进程信息来源
进程信息可从以下来源获取:
-
SHOW PROCESSLIST语句:Section 15.7.7.29, “SHOW PROCESSLIST 语句” -
mysqladmin processlist 命令:Section 6.5.2, “mysqladmin — MySQL 服务器管理程序”
-
INFORMATION_SCHEMAPROCESSLIST表:Section 28.3.23, “INFORMATION_SCHEMA PROCESSLIST 表” -
Performance Schema
processlist表:Section 29.12.21.7, “processlist 表” -
Performance Schema
threads表列名以PROCESSLIST_为前缀:Section 29.12.21.8, “threads 表” -
sysschemaprocesslist和session视图:Section 30.4.3.22, “processlist 和 xsession 视图”
threads 表与 SHOW PROCESSLIST, INFORMATION_SCHEMA PROCESSLIST, 和 mysqladmin processlist 相比如下:
-
访问
threads表不需要互斥锁,并且对服务器性能影响很小。其他来源会产生负面性能影响,因为它们需要互斥锁。注意
截至 MySQL 8.0.22 版本,基于性能模式
processlist表的另一种SHOW PROCESSLIST实现可用,类似于threads表,不需要互斥锁,并具有更好的性能特性。详情请参见第 29.12.21.7 节,“The processlist Table”。 -
threads表显示后台线程,其他来源不显示。它还为每个线程提供其他来源不提供的附加信息,例如线程是前台线程还是后台线程,以及与线程关联的服务器内部位置。这意味着threads表可用于监控其他来源无法监控的线程活动。 -
您可以启用或禁用性能模式线程监控,如第 29.12.21.8 节,“The threads Table”所述。
出于这些原因,使用其他线程信息源之一进行服务器监控的数据库管理员可能希望改为使用threads表进行监控。
sys模式processlist视图以更易访问的格式呈现性能模式threads表中的信息。sys模式session视图呈现有关用户会话的信息,类似于sys模式processlist视图,但过滤掉后台进程。
访问进程列表所需的权限
对于大多数进程信息来源,如果具有PROCESS权限,则可以查看所有线程,即使属于其他用户。否则(没有PROCESS权限),非匿名用户可以访问有关自己线程的信息,但不能访问其他用户的线程,匿名用户无法访问线程信息。
性能模式threads表还提供线程信息,但表访问使用不同的权限模型。请参阅第 29.12.21.8 节,“threads 表”。
进程列表条目内容
每个进程列表条目包含几个信息片段。以下列表使用SHOW PROCESSLIST输出中的标签描述它们。其他进程信息源使用类似的标签。
-
Id是与线程关联的客户端的连接标识符。 -
User和Host表示与线程关联的帐户。 -
db是线程的默认数据库,如果没有选择任何数据库,则为NULL。 -
Command和State表示线程正在执行的操作。大多数状态对应非常快速的操作。如果线程在给定状态下停留了很多秒钟,可能存在需要调查的问题。
以下各节列出了可能的
Command值,以及按类别分组的State值。对于其中一些值,其含义是不言自明的。对于其他值,提供了额外的描述。注意
检查进程列表信息的应用程序应该注意,命令和状态可能会发生变化。
-
Time表示线程在当前状态下已经存在的时间。在某些情况下,线程的当前时间概念可能会发生变化:线程可以使用SET TIMESTAMP = *value*更改时间。对于复制 SQL 线程,该值是最后一个复制事件的时间戳与复制主机的实际时间之间的秒数。请参阅第 19.2.3 节,“复制线程”。 -
Info表示线程正在执行的语句,如果没有执行任何语句,则为NULL。对于SHOW PROCESSLIST,此值仅包含语句的前 100 个字符。要查看完整的语句,请使用SHOW FULL PROCESSLIST(或查询不同的进程信息源)。
10.14.2 线程命令值
线程可以具有以下任一命令值:
-
二进制日志转储这是一个在复制源上的线程,用于向复制发送二进制日志内容。
-
更改用户线程正在执行更改用户操作。
-
关闭语句线程正在关闭准备语句。
-
连接由连接到源的复制接收线程和复制工作线程使用。
-
连接输出复制正在连接到其源。
-
创建数据库线程正在执行创建数据库操作。
-
守护进程此线程是服务器内部的,不是为客户端连接提供服务的线程。
-
调试线程正在生成调试信息。
-
延迟插入线程是延迟插入处理程序。
-
删除数据库线程正在执行删除数据库操作。
-
错误 -
执行线程正在执行准备语句。
-
获取线程正在获取执行准备语句的结果。
-
字段列表线程正在检索表列信息。
-
初始化数据库线程正在选择默认数据库。
-
终止线程正在终止另一个线程。
-
长数据线程在执行准备语句后检索长数据结果。
-
ping线程正在处理服务器 ping 请求。
-
准备线程正在准备一个准备语句。
-
进程列表线程正在生成有关服务器线程的信息。
-
查询在单线程复制应用程序线程执行查询时为用户客户端使用,以及由复制协调器线程使用。
-
退出线程正在终止。
-
刷新线程正在刷新表、日志或缓存,或重置状态变量或复制服务器信息。
-
注册从属线程正在注册一个复制服务器。
-
重置语句线程正在重置准备语句。
-
设置选项线程正在设置或重置客户端语句执行选项。
-
关闭线程正在关闭服务器。
-
休眠线程正在等待客户端发送新语句。
-
统计线程正在生成服务器状态信息。
-
时间未使用。
10.14.3 常规线程状态
原文:
dev.mysql.com/doc/refman/8.0/en/general-thread-states.html
以下列表描述了与一般查询处理相关的线程State值,而���是更专业的活动,如复制。其中许多仅用于在服务器中查找错误。
-
创建后当线程创建表(包括内部临时表)时,会发生这种情况,在创建表的函数结束时。即使由于某些错误而无法创建表,也会使用此状态。
-
修改表服务器正在执行原地
ALTER TABLE的过程中。 -
分析中线程正在计算
MyISAM表的键分布(例如,对于ANALYZE TABLE)。 -
检查权限线程正在检查服务器是否具有执行该语句所需的权限。
-
检查表线程正在执行表检查操作。
-
清理中线程已处理完一个命令,并准备释放内存并重置某些状态变量。
-
关闭表线程正在将更改的表数据刷新到磁盘并关闭已使用的表。这应该是一个快速操作。如果不是,请验证您的磁盘没有满,并且磁盘没有被大量使用。
-
提交更改表到存储引擎服务器已完成一个原地
ALTER TABLE并正在提交结果。 -
将 HEAP 转换为磁盘线程正在将内部临时表从
MEMORY表转换为磁盘上的表。 -
复制到临时表线程正在处理一个
ALTER TABLE语句。此状态发生在具有新结构的表已创建但在将行复制到其中之前。对于处于此状态的线程,性能模式可用于获取有关复制操作进度的信息。请参阅第 29.12.5 节,“性能模式阶段事件表”。
-
复制到组表如果语句具有不同的
ORDER BY和GROUP BY标准,则按组排序行并复制到临时表。 -
复制到临时表服务器正在将数据复制到内存中的临时表。
-
复制到磁盘上的临时表服务器正在将数据复制到磁盘上的临时表。临时结果集变得太大(请参阅第 10.4.4 节,“MySQL 中的内部临时表使用”)。因此,线程正在将临时表从内存转换为基于磁盘的格式以节省内存。
-
创建索引线程正在为
MyISAM表处理ALTER TABLE ... ENABLE KEYS。 -
创建排序索引线程正在处理使用内部临时表解析的
SELECT。 -
创建表线程正在创建表。这包括创建临时表。
-
创建临时表线程正在内存或磁盘上创建临时表。如果表是在内存中创建的,但后来转换为磁盘表,那么在该操作期间的状态是
复制到磁盘上的临时表。 -
从主表中删除服务器正在执行多表删除的第一部分。仅从第一个表中删除,并保存列和偏移量以用于从其他(参考)表中删除。
-
从参考表中删除服务器正在执行多表删除的第二部分,并从其他表中删除匹配的行。
-
discard_or_import_tablespace线程正在处理
ALTER TABLE ... DISCARD TABLESPACE或ALTER TABLE ... IMPORT TABLESPACE语句。 -
end在执行
ALTER TABLE、CREATE VIEW、DELETE、INSERT、SELECT或UPDATE语句的清理之前发生。对于
end状态,可能正在进行以下操作:-
将事件写入二进制日志
-
释放内存缓冲区,包括 blob
-
-
执行中线程已开始执行语句。
-
执行 init_command线程正在执行
init_command系统变量值中的语句。 -
释放项目线程已执行命令。通常在此状态之后是
清理。 -
FULLTEXT 初始化服务器正在准备执行自然语言全文搜索。
-
初始化在执行
ALTER TABLE、DELETE、INSERT、SELECT或UPDATE语句的初始化之前发生。服务器在此状态下采取的操作包括刷新二进制日志和InnoDB日志。 -
已终止有人向线程发送了
KILL语句,并且线程应该在下次检查终止标志时中止。在 MySQL 的每个主要循环中检查该标志,但在某些情况下,线程可能仍需要一段时间才能终止。如果线程被其他线程锁定,那么终止将在其他线程释放锁定时立即生效。 -
锁定系统表线程正在尝试锁定系统表(例如,时区或日志表)。
-
记录慢查询线程正在将语句写入慢查询日志。
-
登录连接线程的初始状态,直到客户端成功验证。
-
管理键服务器正在启用或禁用表索引。
-
打开系统表线程正在尝试打开一个系统表(例如,时区或日志表)。
-
打开表线程正在尝试打开一个表。这应该是一个非常快速的过程,除非有什么阻止打开。例如,一个
ALTER TABLE或LOCK TABLE语句可能会阻止打开一个表,直到语句执行完毕。还值得检查您的table_open_cache��是否足够大。对于系统表,使用
打开系统表状态。 -
优化服务器正在为查询执行初始优化。
-
准备中这种状态发生在查询优化期间。
-
准备修改表服务器正在准备执行一个原地
ALTER TABLE。 -
清除旧的中继日志线程正在删除不需要的中继日志文件。
-
查询结束这种状态发生在处理查询之后但在
释放项目状态之前。 -
从客户端接收服务器正在从客户端读取一个数据包。
-
移除重复项查询使用
SELECT DISTINCT的方式使得 MySQL 无法在早期阶段优化去除重复操作。因此,MySQL 需要额外的阶段在将结果发送给客户端之前去除所有重复行。 -
移除临时表线程在处理
SELECT语句后正在移除内部临时表。如果没有创建临时表,则不使用此状态。 -
重命名线程正在重命名一个表。
-
重命名结果表线程正在处理一个
ALTER TABLE语句,已创建新表,并正在将其重命名以替换原始表。 -
重新打开表线程为表获取了锁,但在获取锁后注意到底层表结构发生了变化。它已释放锁,关闭表,并尝试重新打开它。
-
排序修复修复代码正在使用排序来创建索引。
-
修复完成线程已完成对
MyISAM表的多线程修复。 -
使用键缓存进行修复修复代码正在通过键缓存逐个创建键。这比
排序修复要慢得多。 -
回滚线程正在回滚一个事务。
-
保存状态对于
MyISAM表操作,如修复或分析,线程正在将新表状态保存到.MYI文件头。状态包括诸如行数、AUTO_INCREMENT计数器和键分布等信息。 -
搜索要更新的行线程在更新之前执行第一阶段以查找所有匹配的行。如果
UPDATE正在更改用于查找相关行的索引,则必须执行此操作。 -
发送数据MySQL 8.0.17 之前:线程正在读取和处理
SELECT语句的行,并将数据发送给客户端。因为在此状态下发生的操作往往执行大量的磁盘访问(读取),所以通常是给定查询的生命周期中运行时间最长的状态。MySQL 8.0.17 及更高版本:此状态不再单独指示,而是包含在执行状态中。 -
发送给客户端服务器正在向客户端写入数据包。
-
设置线程正在开始一个
ALTER TABLE操作。 -
为分组排序线程正在进行排序以满足
GROUP BY。 -
为订单排序线程正在进行排序以满足
ORDER BY。 -
排序索引线程正在为更高效地访问
MyISAM表优化操作中的索引页面进行排序。 -
排序结果对于
SELECT语句,这类似于创建排序索引,但适用于非临时表。 -
开始语句执行开始时的第一阶段。
-
统计服务器正在计算统计信息以制定查询执行计划。如果线程在此状态下长时间停留,服务器可能正在执行其他工作而受到磁盘限制。
-
系统锁线程已调用
mysql_lock_tables(),并且线程状态尚未更新。这是一个非常普遍的状态,可能出现的原因很多。例如,线程将请求或正在等待表格的内部或外部系统锁。这可能发生在
InnoDB在执行LOCK TABLES期间等待表级锁时。如果此状态是由于对外部锁的请求而引起的,并且您没有使用访问相同MyISAM表的多个mysqld服务器,您可以使用--skip-external-locking选项禁用外部系统锁。但是,默认情况下已禁用外部锁定,因此这个选项可能没有效果。对于SHOW PROFILE,此状态表示线程正在请求锁(而不是等待锁)。对于系统表,使用
锁定系统表状态。 -
更新线程正在准备开始更新表格。
-
更新中线程正在搜索要更新的行并更新它们。
-
更新主表服务器正在执行多表更新的第一部分。它只更新第一个表,并保存列和偏移量以用于更新其他(参考)表。
-
更新参考表服务器正在执行多表更新的第二部分,并更新其他表中匹配的行。
-
用户锁该线程将请求或正在等待使用
GET_LOCK()函数请求的咨询锁。对于SHOW PROFILE,此状态表示线程正在请求锁(而不是等待锁)。 -
用户休眠该线程已调用
SLEEP()函数。 -
等待提交锁FLUSH TABLES WITH READ LOCK正在等待提交锁。 -
等待处理程序提交该线程正在等待事务提交,而不是查询处理的其他部分。
-
等待表线程收到通知,表的基础结构已更改,需要重新打开表以获取新结构。但是,为了重新打开表,必须等到所有其他线程关闭了相关表。
如果另一个线程在相关表上使用了
FLUSH TABLES或以下语句之一:FLUSH TABLES *tbl_name*,ALTER TABLE,RENAME TABLE,REPAIR TABLE,ANALYZE TABLE或OPTIMIZE TABLE,则会发生此通知。 -
等待表刷新该线程正在执行
FLUSH TABLES并正在等待所有线程关闭其表,或者该线程收到通知,表的基础结构已更改,需要重新打开表以获取新结构。但是,为了重新打开表,必须等到所有其他线程关闭了相关表。如果另一个线程在相关表上使用了
FLUSH TABLES或以下语句之一:FLUSH TABLES *tbl_name*,ALTER TABLE,RENAME TABLE,REPAIR TABLE,ANALYZE TABLE或OPTIMIZE TABLE,则会发生此通知。 -
等待 *lock_type* 锁服务器正在等待获取
THR_LOCK锁或来自元数据锁定子系统的锁,其中*lock_type*表示锁的类型。此状态表示等待
THR_LOCK:等待表级锁
这些状态表示正在等待元数据锁:
-
等待事件元数据锁 -
等待全局读锁 -
等待模式元数据锁 -
等待存储函数元数据锁 -
等待存储过程元数据锁 -
等待表元数据锁 -
等待触发器元数据锁
有关表锁指示器的信息,请参见第 10.11.1 节,“内部锁定方法”。有关元数据锁定的信息,请参见第 10.11.4 节,“元数据锁定”。要查看哪些锁正在阻止锁请求,请使用第 29.12.13 节,“性能模式锁定表”中描述的性能模式锁定表。
-
等待条件一种通用状态,线程正在等待条件变为真。没有可用的具体状态信息。
-
写入网络服务器正在向网络写入数据包。
10.14.4 复制源线程状态
原文:
dev.mysql.com/doc/refman/8.0/en/source-thread-states.html
以下列表显示了在复制源的Binlog Dump线程的State列中可能看到的最常见状态。如果在源上看不到Binlog Dump线程,则意味着复制未运行;也就是说,当前没有副本连接。
在 MySQL 8.0.26 中,对仪表命名进行了不兼容的更改,包括线程阶段的名称,包含术语“master”,已更改为“source”,“slave”,已更改为“replica”,以及“mts”(用于“多线程从属”),已更改为“mta”(用于“多线程应用程序”)。使用这些仪表命名的监控工具可能会受到影响。如果不兼容的更改对您产生影响,请将terminology_use_previous系统变量设置为BEFORE_8_0_26,以使 MySQL Server 使用前面列表中指定对象的旧版本名称。这样可以使依赖旧名称的监控工具继续工作,直到它们可以更新为使用新名称。
使用会话范围设置terminology_use_previous系统变量以支持个别功能,或者使用全局范围以成为所有新会话的默认值。当使用全局范围时,慢查询日志包含旧版本的名称。
-
完成读取一个二进制日志;切换到下一个二进制日志线程已经完成了读取一个二进制日志文件,并正在打开下一个要发送到副本的文件。
-
主已将所有二进制日志发送到从属;等待更多更新来自 MySQL 8.0.26:
源已将所有二进制日志发送到副本;等待更多更新线程已经从二进制日志中读取了所有剩余的更新并将它们发送到副本。线程现在处于空闲状态,等待二进制日志中出现新事件,这些事件是源上发生的新更新的结果。
-
发送二进制日志事件到从属来自 MySQL 8.0.26:
发送二进制日志事件到副本二进制日志由事件组成,其中一个事件通常是一个更新加上一些其他信息。线程已经从二进制日志中读取了一个事件,现在正在将其发送到副本。
-
等待完成终止作为线程停止时发生的一个非常简短的状态。
10.14.5 复制 I/O(接收器)线程状态
原文:
dev.mysql.com/doc/refman/8.0/en/replica-io-thread-states.html
以下列表显示了在副本服务器上的复制 I/O(接收器)线程的State列中最常见的状态。此状态还显示在SHOW REPLICA STATUS(或在 MySQL 8.0.22 之前,SHOW REPLICA STATUS)中显示的Replica_IO_State列中,因此您可以通过使用该语句来了解正在发生的情况。
在 MySQL 8.0.26 中,对仪表命名进行了不兼容的更改,包括线程阶段的名称,包含术语“主”,更改为“源”,“从”,更改为“副本”,以及“mts”(用于“多线程从服务器”),更改为“mta”(用于“多线程应用程序”)。使用这些仪表命名的监控工具可能会受到影响。如果不兼容的更改对您产生影响,请将terminology_use_previous系统变量设置为BEFORE_8_0_26,以使 MySQL 服务器使用前面列表中指定对象的旧版本名称。这使依赖旧名称的监控工具可以继续工作,直到它们可以更新为使用新名称。
使用会话范围设置terminology_use_previous系统变量以支持个别功能,或者使用全局范围作为所有新会话的默认值。当使用全局范围时,慢查询日志包含旧版本的名称。
-
检查主版本来自 MySQL 8.0.26:
检查源版本在与源的连接建立后非常短暂地发生的状态。
-
连接到主服务器来自 MySQL 8.0.26:
连接到源线程正在尝试连接到源。
-
将主事件排队到中继日志来自 MySQL 8.0.26:
将源事件排队到中继日志线程已读取一个事件,并将其复制到中继日志,以便 SQL 线程可以处理它。
-
在失败的 binlog 转储请求后重新连接线程正在尝试重新连接到源。
-
在失败的主事件读取后重新连接来自 MySQL 8.0.26:
在失败的源事件读取后重新连接线程正在尝试重新连接到源。当重新建立连接时,状态变为
等待主服务器发送事件。 -
在主服务器上注册从服务器来自 MySQL 8.0.26:
在源上注册副本在与源的连接建立后非常短暂地发生的状态。
-
请求 binlog 转储在与源建立连接后短暂发生的状态。 线程向源发送请求,请求其二进制日志的内容,从请求的二进制日志文件名和位置开始。
-
等待轮到提交当启用
replica_preserve_commit_order或slave_preserve_commit_order时,副本线程等待旧的工作线程提交时发生的状态。 -
等待主发送事件从 MySQL 8.0.26:
等待源发送事件线程已连接到源并等待二进制日志事件到达。 如果源处于空闲状态,这可能持续很长时间。 如果等待持续了
replica_net_timeout或slave_net_timeout秒,将发生超时。 在那时,线程认为连接已中断并尝试重新连接。 -
等待主更新从 MySQL 8.0.26:
等待源更新在
连接到主或连接到源之前的初始状态。 -
等待副本互斥体退出从 MySQL 8.0.26:
等待副本互斥体退出线程停止时短暂发生的状态。
-
等待从 SQL 线程释放足够的中继日志空间从 MySQL 8.0.26:
等待副本 SQL 线程释放足够的中继日志空间您正在使用非零的
relay_log_space_limit值,并且中继日志已经增长到足够大,使其总大小超过此值。 I/O(接收器)线程正在等待,直到 SQL(应用程序)线程通过处理中继日志内容释放足够的空间,以便可以删除一些中继日志文件。 -
在失败的二进制日志转储请求后等待重新连接如果二进制日志转储请求失败(由于断开连接),线程在睡眠时进入此状态,然后定期尝试重新连接。 可以使用
CHANGE REPLICATION SOURCE TO语句(从 MySQL 8.0.23 开始)或CHANGE MASTER TO语句(在 MySQL 8.0.23 之前)指定重试间隔。 -
在失败的主事件读取后等待重新连接从 MySQL 8.0.26:
在失败的源事件读取后等待重新连接在读取时发生错误(由于断开连接)。线程将根据
CHANGE REPLICATION SOURCE TO语句(从 MySQL 8.0.23 开始)或CHANGE MASTER TO语句(MySQL 8.0.23 之前)设置的秒数(默认为 60)睡眠,然后尝试重新连接。