如何优化MySQL服务器

mysql可算是使用最为广泛的开源数据库,其优化需要先了解在生产环境中的表现,可选择一种监控工具采集关键性能数据,例如 Hyperic HQ ,这里主要记录了一些在生产环境的优化要点,以调整参数和配置为主。

系统因素和启动参数的调节

我们从系统级因素开始,因为必须尽早地进行部分决策以获得较大性能。在其它情况下,快速浏览该节就足够了。但是,了解一下更改该层次的参数能够获得多少性能提高是很有意义的。

使用的操作系统很重要。为了更好地使用多CPU机器,应使用Solaris(因为其线程工作得很好)或Linux(因为2.4和以后的内核有很好的SMP支持)。请注意默认情况旧的Linux内核有一个2GB的文件大小限制。如果有这样的一个内核并且需要文件大于2GB,应得到ext2文件系统的大文件支持(LFS)补丁。其它文件系统例如ReiserFS和XFS没有此2GB限制。

将MySQL用于生产前,我们建议你在想用的平台上对它进行测试。

其它技巧:

· 如果有足够的RAM,可以移除所有的交换设备。有些操作系统即使有自由内存也使用交换设备。

· 使用--skip-external-locking MySQL选项以避免外部锁定。该选项默认开启。

请注意只要你只运行一个服务器,--skip-external-locking选项不影响MySQL的功能。只要记住运行myisamchk之前关闭服务器(或锁定并刷新相关表)。在一些系统上该选项是强制的,因为在任何情况下外部锁定均不工作。

不能使用--skip-external-locking的唯一情况是对相同的数据运行多个MySQL服务器(非客户)的情况,或者如果没有事先告诉服务器刷新并锁定一个表即运行myisamchk来检查(非修复)该表。请注意一般不建议使用多个MySQL服务器来并行访问相同的数据,除了使用MySQL Cluster时。

即使使用--skip-external-locking,仍然可以使用LOCK TABLES和UNLOCK TABLES。

调节服务器参数

可以用这个命令得到mysqld服务器 默认缓存区的大小:

shell> mysqld --verbose --help

这个命令生成所有mysqld选项和可配置变量的列表。输出包括 默认值.

如果有一个mysqld服务器正在运行,通过连接它并执行这个命令,可以看到实际上使用的变量的值:

mysql> SHOW VARIABLES;

还可以通过下面的语句看到运行服务器的统计和状态指标:

mysql>SHOW STATUS;

使用mysqladmin还可以获得系统变量和状态信息:

shell> mysqladmin variables
shell> mysqladmin extended-status

MySQL使用完全可以升级的算法,因此通常运行时可以用很少的内存。然而,通常情况若给MySQL更多的内存性能会更好。

当调节MySQL服务器时,要配置的两个最重要的变量是key_buffer_size和table_cache。在试图更改其它变量前你应先确信已经适当地配置了这些变量。

下面的例子显示了部分典型的不同的运行时配置的变量值。

· 如果至少有256MB内存和许多表,想要在中等数量的客户时获得最大性能,应使用:

· shell> mysqld_safe --key_buffer_size=64M --table_cache=256 \
· --sort_buffer_size=4M --read_buffer_size=1M &

· 如果只有128MB内存和少量表,但仍然要进行大量的排序,可以使用:

· shell> mysqld_safe --key_buffer_size=16M --sort_buffer_size=1M

如果有许多并行连接,交换问题会发生,除非mysqld已经配置成为每个连接分配很少的内存。如果有足够的内存用于所有连接,mysqld会执行得更好。

· 对于少量内存和大量连接,应使用:

· shell> mysqld_safe --key_buffer_size=512K --sort_buffer_size=100K \
· --read_buffer_size=100K &

或甚至为:

shell> mysqld_safe --key_buffer_size=512K --sort_buffer_size=16K \
--table_cache=32 --read_buffer_size=8K \
--net_buffer_length=1K &

如果正对远远大于可用内存的表执行GROUP BY或ORDER BY操作,应增加read_rnd_buffer_size的值以加速排序操作后面的行读取。

如果已经安装了MySQL,support-files目录包含一些不同的my.cnf示例文件:my-huge.cnf、my-大.cnf、my-medium.cnf和my-small.cnf。可以使用这些文件来优化系统。

请注意如果在命令行中为mysqld或mysqld_safe指定一个选项,它只在该次服务器调用中保持有效。要想每次服务器运行时使用该选项,将它放在选项文件中。

要想看参数更改的效果,应执行:

shell> mysqld --key_buffer_size=32M --verbose ---help

变量值列于输出的最后。确保--verbose和---help选项在最后。否则,在命令行中列于它们后面的选项的效果不会反映到输出中。

控制查询优化器的性能

查询优化器的任务是发现执行SQL查询的最佳方案。因为“好”方案和“坏”方案之间的性能差别会巨大(也就是说,秒相对于小时或甚至天),大多数查询优化器,包括MySQL的查询优化器,总或多或少地在所有可能的查询评估方案中搜索最佳方案。对于联接查询,MySQL优化器所调查的可能的方案数随查询中所引用的表的数目呈指数增长。对于小数量的表(典型小于7-10),这不是一个问题。然而,当提交的查询更大时,查询优化所花的时间会很容易地成为服务器性能的主要瓶颈。

查询优化的一个更加灵活的方法是允许用户控制优化器详尽地搜索最佳查询评估方案。一般思想是优化器调查的方案越少,它编译一个查询所花费的时间越少。另一方面,因为优化器跳过了一些方案,它可能错过一个最佳方案。

优化器关于方案数量评估的行为可以通过两个系统变量来控制:

· optimizer_prune_level变量告诉优化器根据对每个表访问的行数的估计跳过某些方案。我们的试验显示该类“有根据的猜测”很少错过最佳方案,并且可以大大降低查询编辑次数。这就是为什么默认情况该选项为on(optimizer_prune_level=1)。然而,如果你认为优化器错过了一个更好的查询方案,则该选项可以关闭(optimizer_prune_level=0),风险是查询编辑花费的时间更长。请注意即使使用该启发,优化器仍然可以探测呈指数数目的方案。

· optimizer_search_depth变量告诉优化器对于每个未完成的“未来的”方案,应查看多深,以评估是否应对它进一步扩大。optimizer_search_depth值较小会使查询编辑次数大大减小。例如,如果optimizer_search_depth接近于查询中表的数量,对12、13或更多表的查询很可能需要几小时甚至几天的时间来编译。同时,如果用optimizer_search_depth等于3或4编辑,对于同一个查询,编译器编译时间可以少于1分钟。如果不能确定合理的optimizer_search_depth值,该变量可以设置为0,告诉优化器自动确定该值。

MySQL如何使用内存

下面的列表中列出了mysqld服务器使用内存的一些方法。在适用的地方,给出了内存相关的系统变量名:

· 键缓存(变量key_buffer_size)被所有线程共享;服务器使用的其它缓存则根据需要分配。参见7.5.2节,“调节服务器参数”。

· 每个连接使用具体线程的空间:

  • 堆栈(默认64KB,变量thread_stack)
  • 连接缓存区(变量net_buffer_length)
  • 结果缓存区(变量net_buffer_length)

连接缓存区和结果缓存区可以根据需要动态扩充到max_allowed_packet。当某个查询运行时,也为当前查询字符串分配内存。

· 所有线程共享相同的基本内存。

· 只有压缩MyISAM表映射到内存。这是因为4GB的32位内存空间不足以容纳大多数大表。当64位地址空间的系统变得越来越普遍后,我们可以增加常规的内存映射支持。

· 对表进行顺序扫描的请求将分配一个缓存区(变量read_buffer_size)。

· 当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读 缓存区(变量read_rnd_buffer_size)以避免硬盘搜索。

· 所有联合在一个令牌内完成,并且大多数联合甚至可以不用临时表即可以完成。大多数临时表是基于内存的(HEAP)表。具有大的记录长度的临时表 (所有列的长度的和)或包含BLOB列的表存储在硬盘上。

如果某个内部heap(堆积)表大小超过tmp_table_size,MySQL可以根据需要自动将内存中的heap表改为基于硬盘的MyISAM表。还可以通过设置mysqld的tmp_table_size选项来增加临时表的大小,或设置客户程序中的SQL选项SQL_BIG_TABLE。

· 进行排序的大多数请求将分配一个排序缓存区,并根据结果集的大小为两个临时文件分配零。

· 几乎所有解析和计算在局部内存中完成。小项目不需要内存,因此避免了普通的慢内存分配和释放。只为不期望的大字符串分配内存;使用函数malloc()和free()来完成。

· 对于每个打开的MyISAM表,索引文件打开一次;数据文件为每个并行运行的线程打开一次。对于每个并行线程,将分配一个表结构、一个每个列的列结构和大小为3 * N的缓存区(其中N是最大行的长度,而不是计算BLOB列)。一个BLOB列需要5至8个字节加上BLOB数据的长度。MyISAM 存储引擎维护一个额外的行缓存区供内部应用。

· 对于每个具有BLOB列的表,将对缓存区进行动态扩大以读入大的BLOB 值。如果你扫描一个表,则分配一个与最大的BLOB值一样大的缓存区。

· 所有使用的表的句柄结构保存在高速缓存中并以FIFO管理。默认情况,高速缓存有64个入口。如果某个表同时被两个运行的线程使用,高速缓存则为该提供两个入口。

· 当并行执行的线程结束时,FLUSH TABLE语句或mysqladmin flush-table命令可以立即关闭所有不使用的表并将所有使用中的表标记为已经关闭。这样可以有效释放大多数使用中的内存。FLUSH TABLE在关闭所有表之前不返回结果。

ps和其它系统状态程序可以报导mysqld使用很多内存。这可以是在不同的内存地址上的线程栈造成的。例如,Solaris版本的ps将栈间未用的内存算作已用的内存。你可以通过用swap -s检查可用交换区来验证它。我们用商业内存漏洞探查器测试了mysqld,因此应该有没有内存漏洞。

MySQL如何使用DNS

当新的客户连接mysqld时,mysqld创建一个新的线程来处理请求。该线程先检查是否主机名在主机名缓存中。如果不在,线程试图解析主机名:

· 如果操作系统支持线程安全gethostbyaddr_r ()和gethostbyname_r()调用,线程使用它们来执行主机名解析。

· 如果操作系统不支持线程安全调用,线程锁定一个互斥体并调用gethostbyaddr()和gethostbyname()。在这种情况下,在第1个线程解锁互斥体前,没有其它线程可以解析不在主机名缓存中的主机名。

你可以用--skip-name-resolve选项启动mysqld来禁用DNS主机名查找。然而,在这种情况下,你只可以使用MySQL中的授权表中的IP号。

如果你有一个很慢的DNS和许多主机,你可以通过用--skip-name-resolve禁用DNS查找或增加HOST_CACHE_SIZE定义(默认值:128)并重新编译mysqld来提高性能。

你可以用--skip-host-cache选项启动服务器来禁用主机名缓存。要想清除主机名缓存,执行FLUSH HOSTS语句或执行mysqladmin flush-hosts命令。

如果你想要完全禁止TCP/IP连接,用--skip-networking选项启动mysqld。

InnoDB性能优化

如果Unix顶层工具或者Windows任务管理器显示,你的数据库的工作负荷的CPU使用率小于70%,则你的工作负荷可能是磁盘绑定的,可能你正生成太多的事务和提交,或者缓冲池太小。使得缓冲池更大 一些会有帮助的,但不要设置缓冲池等于或超过物理内存的80%.

· 把数个修改裹进一个事务里。如果事务对数据库修改,InnoDB在该事务提交时必须刷新日志到磁盘。因为磁盘旋转的速度至多167转/秒,如果磁盘没有骗操作系统的话,这就限制提交的数目为同样的每秒167次。

· 如果你可以接受损失一些最近的已提交事务,你可以设置my.cnf文件里的参数innodb_flush_log_at_trx_commit为0。 无论如何InnoDB试着每秒 刷新一次日志,尽管刷新不被许可。

· 使用大的日志文件,让它甚至与缓冲池一样大。当InnoDB写满日志文件时,它不得不在一个检查点把缓冲池已修改的内容写进磁盘。小日志文件导致许多不必要的吸盘写操作。大日志文件的缺点时恢复时间更长。

· 也让日志缓冲相当大(与8MB相似的数量)。

· 如果你存储变长度字符串,或者列可能包含很多NULL值,则使用VARCHAR列类型而不是CHAR类型。一个CHAR(N)列总是占据N 字节来存储,即使字符串更短或字符串的值是NULL。越小的表越好地适合缓冲池并且减少磁盘I/O。

当使用row_format=compact (MySQL 5.1中 默认的InnoDB记录格式)和可变长度字符集,比如UTF-8或sjis,CHAR(N)将占据可变数量的空间,至少为N 字节。

· 在一些版本的GNU/Linux和Unix上,用Unix的fsync()(InnoDB默认使用的)把文件刷新到磁盘,并且其他相似的方法是惊人的慢。如果你不满意数据库的写性能,你可以试着设置my.cnf里的innodb_flush_method为O_DSYNC,虽然O_DSYNC在多数系统上看起来更慢。

· 当在Solaris 10上,为x86_64架构(AMD Opteron)使用InnoDB存储引擎,重要的是使用forcedirectio选项来安装任何为存储与InnoDB相关的文件而使用的数据系统。(默认在Solaris 10/x86_64上不使用这个文件系统安装选项 )。使用forcedirectio 失败会导致InnoDB在这个平台上的速度和性能严重退化。

· 当导入数据到InnoDB中之时,请确信MySQL没有允许autocommit模式,因为允许autocommit模式会需要每次插入都要 刷新日志到磁盘。要在导入操作规程中禁止autocommit模式,用SET AUTOCOMMIT和COMMIT语句来包住导入语句:

· SET AUTOCOMMIT=0;
· /* SQL import statements ... */
· COMMIT;

如果你使用mysqldump 选项--opt,即使不用SET AUTOCOMMIT和COMMIT语句来包裹,你也使得快速的转储文件被导入到InnoDB表中。

· 小心大宗插入的大回滚:InnoDB在插入中使用插入缓冲来节约磁盘I/O, 但是在相应的回滚中没有使用这样的机制。一个磁盘绑定的回滚可以用相应插入花费时间的30倍来执行。杀掉数据库进程没有是帮助的,因为回滚在服务器启动时 会再次启动。除掉一个失控的回滚的唯一方法是增大缓冲池使得回滚变成CPU绑定且跑得快,或者使用专用步骤,请参阅15.2.8.1节,“强制恢复”。

· 也要小心其它大的磁盘绑定操作。用DROP TABLE或CREATE TABLE来清空一个表,而不是用DELETE FROM tbl_name。

· 如果你需要插入许多行,则使用多行插入语法来减少客户端和服务器之间的通讯开支:

· INSERT INTO yourtable VALUES (1,2), (5,5), ...;

这个提示对到任何表类型的插入都是合法的,不仅仅是对InnoDB类型。

· 如果你在第二个键上有UNIQUE约束,你可以在导入会话中暂时关闭唯一性检查以加速表的导入:

· SET UNIQUE_CHECKS=0;

对于大表,这节约了大量磁盘I/O,因为InnoDB可以使用它的插入缓冲来在一批内写第二个索引记录。

· 如果你对你的表有FOREIGN KEY约束,你可以在导入会话过程中通过关闭外键检查来提速表的导入:

· SET FOREIGN_KEY_CHECKS=0;

对于大表,这可以节约大量的磁盘I/O。

· 如果你经常有对不经常更新的表的重发查询,请使用查询缓存:

· [mysqld]
· query_cache_type = ON
· query_cache_size = 10M

SHOW INNODB STATUS和 InnoDB Monitors

InnoDB包含InnoDB Monitors,它打印有关InnoDB内部状态的信息。你可以使用SQL语句SHOW INNODB STATUS来取标准InnoDB Monitor的输出送到你的SQL客户端。这个信息在性能调节时有用。(如果你正使用mysql事务SQL客户端,如果你用\G替代通常的语句终止符分号,输出结果会更可读 )。关于InnoDB锁定模式的讨论,请参阅15.2.10.1节,“InnoDB锁定模式”。

mysql> SHOW INNODB STATUS\G

另一个使用InnoDB Monitors的方法时让它们不断写数据到mysqld服务器的标准输出。在这种情况下,没有输出被送到客户端。当被打开的时候,InnoDB Monitors每15秒打印一次数据。服务器输出通常被定向到MySQL数据目录里的.err日志。这个数据在性能调节时有用。在Windows上,如果你想定向输出到窗口 而不是到日志文件,你必须从控制台窗口的命令行使用--console选项来启动服务器。

监视器输出包括下列类型的信息:

· 被每个激活事务持有的表和记录锁定

· 事务的锁定等待

· 线程的信号等待

· 未解决的文件I/O请求

· 缓冲池统计数据

· 主InnoDB线程的净化和插入缓冲合并活动

要让标准InnoDB Monitor写到mysqld的标准输出,请使用下列SQL语句:

CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;

监视器可以通过发出下列语句来被停止:

DROP TABLE innodb_monitor;

CREATE TABLE语法正是通过MySQL的SQL解析程序往InnoDB引擎传递命令的方法:唯一有问题的事情是表名字innodb_monitor及它是一个InnoDB表。对于InnoDB Monitor, 表的结构根本不相关。如果你在监视器正运行时关闭服务器,并且你想要再次启动监视器,你必须在可以发出新CREATE TABLE语句启动监视器之前移除表。这个语法在将来的发行版本中可能会改变。

你可以以相似的方式使用innodb_lock_monitor。除了它也提供大量锁定信息之外,它与innodb_monitor相同。一个分离的 innodb_tablespace_monitor打印存在于表空间中已创建文件片断的一个列表,并且确认表空间分配数据结构。此外,有innodb_table_monitor,用它你可以打印InnoDB内部数据词典的内容。

如果TRANSACTIONS节报告锁定等待,你的应用程序可能有锁定竞争。输出也帮助跟踪事务死锁的原因。

· SEMAPHORES节报告等待一个信号的线程和关于线程对一个互斥体或rw-lock信号需要多少次回滚或等待的统计数据。大量等待信号的线程可能是磁盘I/O的结果或者InnoDB内竞争问题的结果。竞争可能是因为查询的严重并行,或者操作系统线程计划的问题。设置innodb_thread_concurrency小于默认值在这种情况下是有帮助的。

· BUFFER POOL AND MEMORY节给你关于页面读写的统计数据。你可以从这些数计算你当前的查询正做多少文件数据I/O操作。

· ROW OPERATIONS节显示主线程正在做什么。

InnoDB发送诊断输出到stderr或文件,而不是到stdout或者固定尺寸内存缓冲,以避免 底层缓冲溢出。作为一个副效果,SHOW INNODB STATUS的输出每15秒钟写到一个状态文件。这个文件的名字是innodb_status.pid,其中pid 是服务器进程ID。这个文件在MySQL数据目录里创建。正常关机之时,InnoDB删除这个文件。如果发生不正常的关机, 这些状态文件的实例可能被展示,而且必须被手动删除。在移除它们之前,你可能想要检查它们来看它们是否包含有关不正常关机的原因的有用信息。仅在配置选项innodb_status_file=1被设置之时,innodb_status.pid文件被创建。