你可以通过査看MySQL的状态来回答许多关于MySQL的问题。MySQL以多种方式来暴露服务器内部信息。最新的是MySQL5.5中的PERFORMANCE_SCHEMA库,而标准的INFORMATION_SCHEMA库从MySQL5.0就已开始存在,此外实际上一直存在一系列的SHOW命令。有些通过SHOW命令获取的信息并不在INRFORMATION_SCHEMA中存在。
对你的挑战是,问题到底是什么,如何获取需要的信息,如何解释它。尽管MySQL允许你査看许多服务器内部发生的信息,但使用这些信息并不总是简单的。理解它需要耐心、经验,并要准备好参阅MySQL用户手册。同样,好的工具也非常有用。
这个附录大部分是参考材料,但也有许多关于服务器内部功能的信息,特别是在关于InnoDB的小节中。
1.系统变量
MySQL通过SHOW VARIABLES SQL命令显露了许多系统变量,你可以在表达式中使用这些变量,或在命令行中通过mysqladmin variables试验。自MySQL5.1起,可以通过访问INF0RMATI0N_SCHEMA库中的表来获取这些信息。
这些变量反映了一系列配置信息,例如服务器的默认存储引擎、可用的时区、连接的排序规则(collation)和启动参数。
1.1 SHOW STATUS
SHOW STATUS命令会显示每个服务器变量的名字和值。和上面讲的服务器参数不一样,状态变量是只读的。可以在MySQL客户端里运行SHOW STATUS或在命令行里运行mysqladmin extended-status来査看这些变量。如果使用SQL命令,可以使用LIKE或WHERE来限制结果。可以用LIKE对变量名做标准模式匹配。命令将返回一个结果表,但不能对它排序,与另外一个表做联合操作,或像对MySQL表一样做一些事情。在MySQL 5.1或更新版本中,可以直接从INFORMATION_SCHEMA.GLOBAL_STATUS and INFORMATION_SCHEMA.SESSION_STATUS
SHOW STATUS的行为自MySQL 5.0后有了非常大的改变,但是如果你没有足够细致地观察,可能不会注意到。5.0之前的版本只有全局变量,5.1及以后的版本中,有的变量是全局的,有的变量是连接级别的。因此,SHOW STATUS混杂了全局和会话变量。其中许多变量有双重域:既是全局变量,也是会话变量,它们拥有相同的名字。现在SHOW STATUS默认也显示会话变量,因此,如果你习惯于使用SHOW STATUS来査看全局变量,则需要改为运行SHOW GLOBAL STATUS査看。
有上百个状态变量。大部分要么是计数器,要么包含某些状态指标的当前值。每次MySQL做一些事情都会导致计数器的增长,比如开始初始化一个全表扫描(Select_scan)。度量值,例如打开的到服务器连接数量(Threads_connected),可能增长和减少。有时候几个变量貌似指向相同的事情,例如Connections(尝试连接到服务器的连接数量)和Threads_connected;在本例下,变量是关联的,但类似的名字并不总是隐含某种关系。
变量采用无符号整型存储。它们在32位编译系统上用4个字节(byte),而在64位环境上用8个字节,并且当达到最大值后会重新从0开始。如果你增量地监测这些变量,可能需要观察并修正这个绕回处理;你也要意识到如果服务器已经运行很长一段时间,可能会有比预期更小的值,这是因为这些变量值已经被重置为零。(在64位编译系统上基本不会出现。)
如果想对服务器的工作负载有一个大体上的了解,可以将相关的一组变量放在一起査看和对比-例如,一起査看所有的Select_*变量,或所有的Handler_*变量。如果使用innotop,在Command Summary模式下査看更简单,但也可以通过类似mysqladmin extended –r –i60 | grep Handler_的命令手动完成,一下是一个检测服务器上innotop对Select_*变量的显示。
____________________ Command Summary _____________________ Name Value Pct Last Incr Pct Select_scan 756582 59.89% 2 100.00% Select_range 497675 39.40% 0 0.00% Select_full_join 7847 0.62% 0 0.00% Select_full_range_join 1159 0.09% 0 0.00% Select_range_check 1 0.00% 0 0.00%
前两列是自服务器启动后的值,最后两列是自上次刷新后的值(在本例中是10s之前)。百分比是与打印输出中显示的总值相比较,而不是与所有査询的总值相比。
査看一组变量的当前值、上一次査询的值,以及它们之间的差值,可以使用Percona Toolkit中的pt-mext工具,或Shlomi Noach写的简洁的査询。
SELECT STRAIGHT_JOIN LOWER(gs0.VARIABLE_NAME) AS variable_name, gs0.VARIABLE_VALUE AS value_0, gs1.VARIABLE_VALUE AS value_1, (gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE) AS diff, (gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE) / 10 AS per_sec, (gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE) * 60 / 10 AS per_min FROM ( SELECT VARIABLE_NAME, VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS UNION ALL SELECT '', SLEEP(10) FROM DUAL ) AS gs0 JOIN INFORMATION_SCHEMA.GLOBAL_STATUS gs1 USING (VARIABLE_NAME) WHERE gs1.VARIABLE_VALUE <> gs0.VARIABLE_VALUE; +-----------------------+---------+---------+------+---------+---------+ | variable_name | value_0 | value_1 | diff | per_sec | per_min | +-----------------------+---------+---------+------+---------+---------+ | handler_read_rnd_next | 2366 | 2953 | 587 | 58.7 | 3522 | | handler_write | 2340 | 3218 | 878 | 87.8 | 5268 | | open_files | 22 | 20 | −2 | −0.2 | −12 | | select_full_join | 2 | 3 | 1 | 0.1 | 6 | | select_scan | 7 | 9 | 2 | 0.2 | 12 | +-----------------------+---------+---------+------+---------+---------+
最有帮助的是査看整个过程最后几分钟所有这些变量值和度量值,査看自服务器启动后的总值也同样有用。
接下来是对SHOW STATUS中所看到的各种变量的概述,但不是一个详尽的列表。对于给定变量的详情,最好査询MySQL用户手册,详见http://dev.mysql.com/doc/en/mysqld-option-tables.html.当我们讨论一组以相同前缀开头的相关变量时,我们指的是“<前綴>_*”这样的变量。
1.1.1 线程和连接统计
这些变量用来跟踪尝试的连接、退出的连接、网络流量和线程统计。
- Connections, Max_used_connections, Threads_connected
- Aborted_clients, Aborted_connects
- Bytes_received, Bytes_sent
- Slow_launch_threads, Threads_cached, Threads_created, Threads_running
如果Aborted_connects不为0,可能意味着网络有问题或某人尝试连接但失败(可能用户指定了错误的密码或无效的数据库,或某个监控系统正在打开TCP的3306端口来检测服务器是否活着)。如果这个值太高,可能有严重的副作用:导致MySQL阻塞一个主机。
Aborted_clients有类似的名字但意思完全不同。如果这个值增长,一般意味着曾经有一个应用错误,例如程序在结束之前忘记正确地关闭MySQL连接。这一般并不表明有大问题。
1.1.2 二进制日志状态
Binlog_cache_use和Binlog_cache_disk_use状态变量显示了在二进制日志缓存中有多少事务被存储过,以及多少事务因超过二进制日志缓存而必须存储到一个临时文件中。MySQL 5.5还包含Binlog_stmt_cache_use和Binlog_stmt_cache_disk_use,显示了非事务语句相应的度量值。所谓的“二进制日志缓存命中率”往往对配置二进制日志缓存的大小并没有参考意义。
1.1.3 命令计数器
Com_*变量统计了每种类型的SQL或C API命令发起过的次数。例如,Com_select统计 了SELECT语句的数量,Com_change_db统计一个连接的默认数据库被通过USE语句或C API调用更改的次数。Questions变量统计总査询量和服务器收到的命令数。然而,它并不完全等于所有Com_*变量的总和,这与査询缓存命中、关闭和退出的连接,以及其他可能的因素有关。
Com_admin_commands状态变量可能非常大。它不仅计数管理命令,并且还包括对MySQL实例的Ping请求。这些请求通过C API发起,并且一般来自客户端代码,例如下面的Perl代码。
my $dbh = DBI->connect(...); while ( $dbh && $dbh->ping ) { # Do something }
这些Ping请求是“垃圾”査询。它们往往不会对服务器产生许多负载,但仍然是个浪费,因为网络回路时间会增加应用的响应时间。我们曾经看到ORM系统(Ruby on Rails立即跃入脑海)在每次査询之前Ping服务器,而这是无意义的;Ping服务器然后再査询是一个“跳跃之前看一下”设计模式的典型例子,它会产生竞争条件。我们同样看到过在每次查询之前更改默认库的数据库抽象函数库,这也会产生大量的Com_change_db命令。最好消除这两种做法。
1.1.4 临时文件和表
可以通过下列命令査看MySQL创建临时表和文件的计数。
mysql> SHOW GLOBAL STATUS LIKE 'Created_tmp%';
这显示了关于隐式临时表和文件的统计——执行査询时内部创建。在Percona Server中,同样有展示晁式临时表(即由用户通过CREATE TEMPORARY TABLE所创建)的命令。
mysql> SHOW GLOBAL TEMPORARY TABLES;
1.1.5 句柄操作
句柄API是MySQL和存储引擎之间的接口。Handlers_*变量用于统计句柄操作,例如MySQL请求一个存储引擎来从一个索引中读取下一行的次数。可以通过下列命令査看这些变量。
mysql> SHOW GLOBAL STATUS LIKE 'Handler_%';
1.1.6 MyISAM键缓冲
Key_*变量包含度量值和关于MyISAM键缓冲的计数。可以通过下列命令査看这些变量。
mysql> SHOW GLOBAL STATUS LIKE 'Key_%';
1.1.7 文件描述符
如果你主要使用MyISAM存储引擎,那么Open_*变量揭示了MySQL每隔多久会打开每个表的.frm、.MYI和.MYD文件。InnoDB保持所有的数据在表空间文件中,因此如果你主要使用InnoDB,那么这些变量并不精确。可以通过下列命令査看Open_*变量。
mysql> SHOW GLOBAL STATUS LIKE 'Open_%';
1.1.8 查询缓存
通过査询Qcache_*状态变量可以检査査询缓存。
mysql> SHOW GLOBAL STATUS LIKE 'Qcache_%';
1.1.9 SELECT类型
Select_*变量是特定类型的SELECT査询的计数器。它们能帮助你了解使用各种査询计划的SELECT査询比率。不幸的是,并没有关于其他査询类型的状态变量,例如UPDATE和REPLACE;然而,可以看一下Handler_*状态变量(前面讨论过)大致了解非SELECT査询的相对数量。要査看所有Select_*变量,使用下列命令。
mysql> SHOW GLOBAL STATUS LIKE 'Select_%';
以我们的判断,Select_*状态变量可以按花费递增的顺序如下排列。
Select_range
在第一个表上扫描一个索引区间的联接数目。
Select_scan
扫描整个第一张表的联接数目。如果第一个表中每行都参与联接,这样计数并没有问题;如果你并不想要所有行但又没有索引以査找到所需要的行,那就糟糕了。
Select_full_range_join
使用在表n中的一个值来从表n+1中通过参考索引的区间内获取行所做的联接数。这个值或多或少比Select_scan开销多些,具体多少取决于査询。
Select_range_check
在表n+1中重新评估表n中的每一行的索引是否开销最小所做的联接数。这一般意味着在表n+1中对该联接而言并没有有用的索引。这个査询有非常髙的额外幵销。
Select_full_join
交叉联接或并没有条件匹配表中行的联接的数目。检测的行数是每个表中行数的乘积。这通常是个坏事情。
最后两个变量一般并不快速地增长,如果快速增长,则可能表明一个“糟糕”的査询引入到了系统中。
1.1.10 排序
在前面几章中我们已经讲了许多MySQL的排序优化,因此你应该知道排序是如何工作的。当MySQL不能使用一个索引来获取预先排序的行时,必须使用文件排序,这会增加Sort_*状态变量。除Sort_merge_passes外,你可以只是增加MySQL会用来排序的索引以改变这些值。Sort_merge_passes依赖sort_buffer_size服务器变量(不要与myisam_sort_buffer_size服务器变量相混淆)。MySQL使用排序缓冲来容纳排序的行块。当完成排序后,它将这些排序后的行合并到结果集中,增加Sort_merge_passes,并且用下一个待排序的行块填充缓存。然而,使用这个变量来指导排序缓存的大小并不是个好方法.
可以通过以下命令査看所有的Sort_*变量。
mysql> SHOW GLOBAL STATUS LIKE 'Sort_%';
当MySQL从文件排序结果中读取已经排好序的行并返回给客户端时,Sort_scan和Sort_range变量会增长。不同点仅在于:前者是当査询计划导致Select_scan增加时增加,而后者是当Select_range增加时增加。二者的实现和开销完全一样;仅仅指示了导致排序的査询计划类型。
1.1.11 表锁
Table_locks_immediate和Table_locks_waited变量可告诉你有多少锁被立即授权,有多少锁需要等待。但请注意,它们只是展示了服务器级别锁的统计,并不是存储引擎级的锁统计。
1.1.12 InnoDB相关
Innodb_*变量展示了SHOW ENGINE INNODB STATUS中包含的一些数据。这些变量会按名字分组:Innodb_buffer_pool_*,Innodb_log_*,等等。稍后我们在检査完SHOW ENGINE INNODB STATUS后会更多地讨论InnoDB内幕。
这些变量存在于MySQL5.0或更新版本中,它们有重要的副作用:它们会创建一个全局锁,然后在释放该锁之前遍历整个InnoDB缓冲池。同时,另外一些线程也会遇到该锁而阻塞,直到它被释放。这歪曲了一些状态值,比如Threads_running,因此,它们看起来比平常更髙(可能高许多,取决于系统此时有多忙)。当运行SHOW ENGINE INNODB STATUS或通过INF0RMATI0N_SCHEMA表(在MySQL5.0或更新版本中,SHOW STATUS和SHOW VARIABLES与对INF0RMATI0N_SCHEMA表的査询在幕后映射了起来)访问这些统计时,有相同的副作用。
因此,这些操作在这些版本的MySQL中会更加昂贵——检查服务器状态太频繁(例如, 每秒一次)可能会显著增加负载。使用SHOW STATUS LIKE也无济于事,因为它要获取所有的状态然后再进行过滤。
MySQL5.5中相比5.1有更多的变量,在Percona Server中更多。
1.1.13 插件相关
MySQL5.1和更新的版本中支持可插拔的存储引擎,并在服务器内对存储引擎提供了注册它们自己的状态和配置变量的机制。如果你在使用一个可插拔的存储引擎,也许会看到许多插件特有的变量。类似的变量总是以插件名开头。
1.2 SHOW ENGINE INNODB STATUS
InnoDB存储引擎在SHOW ENGINE INNODB STATUS输出中,老版本中对应的是SHOW INNODB STATUS,显示出了大量的内部信息。
不像其他大部分SHOW命令,它的输出就是单独的一个字符串,没有行和列。它分为很多小段,每一段对应了InnoDB存储引擎不同部分的信息,其中有一些信息对于InnoDB开发者来说是非常有用的,但是,许多信息,如果你试着去理解,并且应用到高性能InnoDB调优的时候,你会发现它们非常有趣——甚至是非常必要的。
输出内容包含了一些平均值的统计信息,例如fsync()每秒调用次数。这些平均值是自上次输出结果生成以来的统计数,因此,如果你正在检査这些值,那就要确保已经等待了30s左右的时间,使两次采样之间积累起足够长的统计时间并多次采样,检查计数器变化从而弄清其行为。并不是所有的输出都会在一个时间点上生成,因而也不是所有显示出来的平均值会在同一时间间隔里重新计算一遍。而且,InnoDB有一个内部复位间隔,而它是不可预知的,各个版本也不一样。你应该检査一下输出,看看有哪些平均值在这个时间段里生成,因为每次采样的时间间隔不总是相同的。
这里面有足够的信息可供手工计算出大多数你想要的统计信息。但是,如果这时有一款监控工具,例如innotop——它能为你计算出增量差值和平均值,那将是非常有用的。
1.2.1 头部信息
第一段是头部信息,它仅仅声明了输出开始,其内容包括当前的日期和时间,以及自上次输出以来经过的时长。下列第2行是当前日期和时间。第4行显示的是计算出这一平均值的时间间隔,即自上次输出以来的时间,或者是距离上次内部复位的时长。
1 ===================================== 2 070913 10:31:48 INNODB MONITOR OUTPUT 3 ===================================== 4 Per second averages calculated from the last 49 seconds
1.2.2 SEMAPHORES
如果有高并发的工作负载,你就要关注下接下来的段:SEMAPHORES(信号量)。它包含了两种数据:事件计数器,以及可选的当前等待线程的列表。如果有性能上的瓶颈,可以使用这些信息来找出瓶颈。不幸的是,想知道怎么使用这些信息还是有一点复杂。下面是一些输出样例。
1 ---------- 2 SEMAPHORES 3 ---------- 4 OS WAIT ARRAY INFO: reservation count 13569, signal count 11421 5 --Thread 1152170336 has waited at ./../include/buf0buf.ic line 630 for 0.00 seconds the semaphore: 6 Mutex at 0x2a957858b8 created file buf0buf.c line 517, lock var 0 7 waiters flag 0 8 wait is ending 9 --Thread 1147709792 has waited at ./../include/buf0buf.ic line 630 for 0.00 seconds the semaphore: 10 Mutex at 0x2a957858b8 created file buf0buf.c line 517, lock var 0 11 waiters flag 0 12 wait is ending 13 Mutex spin waits 5672442, rounds 3899888, OS waits 4719 14 RW-shared spins 5920, OS waits 2918; RW-excl spins 3463, OS waits 3163
第4行给出了关于操作系统等待数组的信息,它是一个“插槽”数组。InnoDB在数组里为信号量保留了一些插槽,操作系统用这些信号量给线程发送信号,使线程可以继续运行,以完成它们等着做的事情。这一行还显示出InnoDB使用了多少次操作系统的等待。保留计数(reservation count)显示了InnoDB分配插槽的频度,而信号计数(signal count)衡量的是线程通过数组得到信号的频度。操作系统的等待相对于空转等待(spin wait)要更昂贵一些,我们即将看到这一点。
第5〜12行显示的是当前正在等待互斥量的InnoDB线程。在这个例子里显示出有两个线程正在等待,每一个都是以“-- Thread <数字> has waited...”开始的。这一段应该是空的,除非服务器运行着高并发的工作负载,促使InnoDB采取让操作系统等待的措施。除非你对InnoDB源代码很熟悉,否则这里看到的最有用的信息是发生线程等待的代码文件名。这就给了你一个提示:在InnoDB内部哪里才是热点。举例来说,如果看到许多线程都在一个名为buf0buf.ic的文件上等待着,那就意味着你的系统里存在着缓冲池竞争。这个输出信息还显示了这些线程等待了多长的时间,其中“waiters flag”显示了有多少个等待者正在等待同一个互斥量。
文本“wait is ending”意味着这个互斥量实际上已经被释放了,但操作系统还没把线程调度过来运行。
你可能想知道InnoDB真正等待的是什么。InnoDB使用了互斥量和信号量来保护代码的临界区,例如,限定每次只能有一个线程进入临界区,或者是当有活动的读时,就限制写入等。在InnoDB代码里有很多临界区,在合适的条件下,它们都可能出现在那里。常常能见到的一种情形就是获取缓冲池分页的访问权。
在等待线程的列表之后,第13和14行显示了更多的事件计数器。第13行显示的是跟互斥量相关的几个计数器,第14行用于显示读/写共享和排他锁的计数器。在每一个情形中,都能看到InnoDB依靠操作系统等待的频度。
InnoDB有着一个多阶段等待策略。首先,它会试着对锁进行空等待。如果经过了一个预设的空转等待周期(设置innodb_sync_spin_loops配置变量指令)之后还没有成功,那就会退到更昂贵更复杂的等待数组中。
空转等待的成本相对比较低,但是它们要不停地检査一个资源是否能被锁定,这种方式会消耗CPU周期。但是,这没有听起来那么糟糕,因为当处理器在等待I/O时,一般都有一些空闲的CPU周期可用,即使是没有空闲的CPU周期,空等也要比其他方式更加廉价一些。然而,当另外一条线程能做一些事情时,空转等待也还会独占处理器。
空转等待的替换方案就是让操作系统做上下文切换,这样,当这个线程在等待时,另外一个线程就可以被运行,然后,通过等待数组里的信号量发出信号,唤醒那个沉睡的线程。通过信号量来发送信号是比较有效率的,但是上下文切换就很昂贵,这很快就会积少成多:每秒钟几千次的切换会引发大量的系统开销。
你可以通过改变系统变量innodb_sync_spin_1oops的值,试着在空转等待与操作系统等待之间达成平衡。不要担心空转等待,除非你在每一秒里会看到许多空转等待(大概是几十万这个水平)。这经常需要理解源代码或咨询专家才能解决。同样也可以考虑使用Performance Schema,或看一下SHOW ENGINE INNODB MUTEX。
1.2.3 LATEST FOREIGN KEY ERROR
下一段,即LATEST FOREIGN KEY ERROR,一般不会出现,除非你的服务器上有外键错误。在源代码里有许多地方会生成这样的输出,具体取决于错误的类型。有时问题在于事务在插入、更新或删除一条记录时要寻找到父行或子行。还有些时候是当InnoDB尝试增加或删除一个外键,或修改一个已经存在的外键时,发现表之间类型不匹配。
这部分输出对于调试与InnoDB往往不明确的外键错误相对应的准确原因非常有帮助。 让我们看几个例子。首先,创建有外键关系的两个表,然后插入少量数据。
CREATE TABLE parent ( parent_id int NOT NULL, PRIMARY KEY(parent_id) ) ENGINE=InnoDB; CREATE TABLE child ( parent_id int NOT NULL, KEY parent_id (parent_id), CONSTRAINT child_ibfk_1 FOREIGN KEY (parent_id) REFERENCES parent (parent_id) ) ENGINE=InnoDB; INSERT INTO parent(parent_id) VALUES(1); INSERT INTO child(parent_id) VALUES(1);
有两种基本的外键错误。以某种可能违反外键约束关系的方法增加、更新或删除数据,将导致第一类错误。例如,以下是当我们从父表中删除行时发生的事情。
DELETE FROM parent; ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test/child`, CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`parent_id`))
错误信息相当直接明了,对所有由增加、更新或删除不匹配的行导致的错误都会看到相似的信息。下面是SHOW ENGINE INNODB STATUS的输出。
1 ------------------------ 2 LATEST FOREIGN KEY ERROR 3 ------------------------ 4 070913 10:57:34 Transaction: 5 TRANSACTION 0 3793469, ACTIVE 0 sec, process no 5488, OS thread id 1141152064 updating or deleting, thread declared inside InnoDB 499 6 mysql tables in use 1, locked 1 7 4 lock struct(s), heap size 1216, undo log entries 1 8 MySQL thread id 9, query id 305 localhost baron updating 9 DELETE FROM parent 10 Foreign key constraint fails for table `test/child`: 11 ' 12 CONSTRAINT `child_ibfk_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`parent_ id`) 13 Trying to delete or update in parent table, in index `PRIMARY` tuple: 14 DATA TUPLE: 3 fields; 15 0: len 4; hex 80000001; asc ;; 1: len 6; hex 00000039e23d; asc 9 =;; 2: len 7; hex 000000002d0e24; asc - $;; 16 17 But in child table `test/child`, in index `parent_id`, there is a record: 18 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 19 0: len 4; hex 80000001; asc ;; 1: len 6; hex 000000000500; asc ;;
第4行显示了最近一次外键错误的日期和时间。第5〜9行显示了关于破坏外键约束的事务详情。后面会再解释这些行。第10〜19行显示了发现错误时InnoDB正尝试修改的准确数据。输出中有许多是转换成可打印格式的行数据。关于这点我们同样会在后面再加以说明。
到目前为止还没有什么问题,但有另外一类的外键错误,可能会让调试更难。以下是当我们尝试修改父表时所发生的。
ALTER TABLE parent MODIFY parent_id INT UNSIGNED NOT NULL; ERROR 1025 (HY000): Error on rename of './test/#sql-1570_9' to './test/parent' (errno: 150)
这就没有那么清楚了,但SHOW ENGINE INNODB STATUS的文本给了些指引信息。
1 ------------------------ 2 LATEST FOREIGN KEY ERROR 3 ------------------------ 4 070913 11:06:03 Error in foreign key constraint of table test/child: 5 there is no index in referenced table which would contain 6 the columns as the first columns, or the data types in the 7 referenced table do not match to the ones in table. Constraint: 8 , 9 CONSTRAINT child_ibfk_1 FOREIGN KEY (parent_id) REFERENCES parent (parent_id) 10 The index in the foreign key in table is parent_id 11 See http://dev.mysql.com/doc/refman/5.0/en/innodb-foreign-key-constraints.html 12 for correct foreign key definition.
本例中的错误是数据类型不同。外键列必须有完全相同的数据类型,包括任何修饰符(例如本例中的UNSIGNED,这也是问题所在)。当看到1025错误并不理解为什么时,最好査看SHOW ENGINE INNODB STATUS。
在每次有新错误时,外键错误信息都会被重写。Percona Toolkit中的pt-fk-error-logger工具可以保存这些信息以供后续分析。
1.2.4 LATEST DETECTED DEADLOCK
跟上面的外键部分一样,LATEST DETECTED DEADLOCK部分也只有当服务器内有死锁时才会出现。死锁错误信息同样在每次有新错误时都会重写,Percona Toolkit中的pt-deadlock-logger工具可以保存这些信息以供后续分析。
死锁在等待关系图里是一个循环,就是一个锁定了行的数据结构又在等待别的锁。这个循环可以任意地大。InnoDB会立即检测到死锁,因为每当有事务等待行锁的时候,它都会去检查等待关系图里是否有循环。死锁的情况可能会比较复杂,但是,这一部分只显示了最近两个死锁的情况,它们在各自的事务里执行的最后一条语句,以及它们在图里形成循环锁的信息。在这个循环里你看不到其他事务,也看不到在事务里早先可能真正获得了锁的语句。尽管如此,通常还是可以通过査看这些输出结果来确定到底是什么引起了死锁。
在InnoDB里实际上有两种死锁。第一种就是人们常常碰到的那种,它在等待关系图里是一个真正的循环。另外一种就是在一个等待关系图里,因代价昂贵而无法检查它是不是包含了循环。如果InnoDB要在关系图里检查超过100万个锁,或者在检査过程中,InnoDB要重做200个以上的事务,那它就会放弃,并宣布这里有一个死锁。这些数值都是硬编码在InnoDB代码里的常量,无法配置(如果你愿意,可以在代码里更改这些数值,然后重新编译)。当InnoDB的检査工作超过这个极限后,它就会引发一个死锁,这时你就可以在输出里看到一条信息“TOO DEEP OR LONG SEARCH IN THE LOCK TABLE WAITS-FOR GRAPH,’。
InnoDB不仅会打印出事务和事务持有及等待的锁,而且还有记录本身。这些信息主要对于InnoDB开发者有用,但目前也没有办法禁止显示。不幸的是,它会变得很大,以致超过为输出结果预留的长度,使你无法看到下面几段输出信息。对此唯一的补救办法是,制造一个小的死锁来替换那个大的死锁,或者使用Percona Server,该服务器软件增加了配置变量来抑制过于详尽的文本。
下面是一个死锁信息的样例。
1 ------------------------ 2 LATEST DETECTED DEADLOCK 3 ------------------------ 4 070913 11:14:21 5 *** (1) TRANSACTION: 6 TRANSACTION 0 3793488, ACTIVE 2 sec, process no 5488, OS thread id 1141287232 starting index read 7 mysql tables in use 1, locked 1 8 LOCK WAIT 4 lock struct(s), heap size 1216 9 MySQL thread id 11, query id 350 localhost baron Updating 10 UPDATE test.tiny_dl SET a = 0 WHERE a <> 0 11 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: 12 RECORD LOCKS space id 0 page no 3662 n bits 72 index `GEN_CLUST_INDEX` of table `test/tiny_dl` trx id 0 3793488 lock_mode X waiting 13 Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 14 0: len 6; hex 000000000501 ...[ omitted ] ... 15 16 *** (2) TRANSACTION: 17 TRANSACTION 0 3793489, ACTIVE 2 sec, process no 5488, OS thread id 1141422400 starting index read, thread declared inside InnoDB 500 18 mysql tables in use 1, locked 1 19 4 lock struct(s), heap size 1216 20 MySQL thread id 12, query id 351 localhost baron Updating 21 UPDATE test.tiny_dl SET a = 1 WHERE a <> 1 22 *** (2) HOLDS THE LOCK(S): 23 RECORD LOCKS space id 0 page no 3662 n bits 72 index `GEN_CLUST_INDEX` of table `test/tiny_dl` trx id 0 3793489 lock mode S 24 Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 25 0: ... [ omitted ] ... 26 27 *** (2) WAITING FOR THIS LOCK TO BE GRANTED: 28 RECORD LOCKS space id 0 page no 3662 n bits 72 index `GEN_CLUST_INDEX` of table `test/tiny_dl` trx id 0 3793489 lock_mode X waiting 29 Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0 30 0: len 6; hex 000000000501 ...[ omitted ] ... 31 32 *** WE ROLL BACK TRANSACTION (2)
第4行显示的是死锁发生的时间,第5〜10行显示的是死锁里的第一个事务的信息。在下一节里,我们会详尽地解释这些输出的含义。
第11〜15行显示的是当死锁发生时,事务1正在等待的锁。我们忽略了其中第14行的信息,那是因为这只对调试才有用。这里要特别注意的内容是第12行,它告诉你这个事务正在等待对test.tiny_dl表中的GEN_CLUST_INDEX索引上排他锁(X锁)。
第16〜21行显示的是第二个事务的状态,第22〜26行显示的是该事务持有的锁。为了简洁起见,在第25行有几条记录已经被我们删去了。这些记录里有一条就是第一个事务正在等待的那一条。最后,第27〜31行显示了它正在等待的是哪一个锁。
当一个事务持有了其他事务需要的锁,同时又想获取其他事务持有的锁时,等待关系图上就会产生循环了。InnoDB不会显示所有持有和等待的锁,但是,它显示了足够的信息来帮你确定:査询操作正在使用哪些索引。这对于你确定是否能避免死锁有着极大的价值。
如果能使两个査询对同一个索引朝同一个方向进行扫描,就能降低死锁的数目,因为,査询在同一顺序上请求锁的时候不会创建循环。有时候,这是很容易做到的,举例来说,如果要在一个事务里更新许多条记录,就可以在应用程序的内存里把它们按主键进行排序,然后,再用同样的顺序更新到数据库,这样就不会有死锁的发生。但是在另一些时候,这个方法也是行不通的(例如有两个进程使用了不同的索引区间操作同一张表的时候)。
第32行显示的是哪个事务被选中成为死锁的牺牲品。InnoDB会把看上去最容易回滚(就是更新的记录数最少的)的事务选为牺牲品。
检测这些常规日志,从中找出线程所涉及的査询,然后看一下到底是什么导致死锁,这非常有用。下节将介绍在哪里可以査找到死锁输出中的线程ID。
1.2.5 TRANSACTIONS
本节包含了一些关于InnoDB事务的总结信息,紧随其后是当前活跃事务列表。以下是前几行信息(头部)。
1 ------------ 2 TRANSACTIONS 3 ------------ 4 Trx id counter 0 80157601 5 Purge done for trx's n:o <0 80154573 undo n:o <0 0 6 History list length 6 7 Total number of lock structs in row lock hash table 0
输出会因MySQL版本不同而变化,但至少包括如下几点。
- 第4行:当前事务的ID,这是一个系统变量,每创建一个新事务都会增加。
- 第5行:这是InnoDB清除旧MVCC行时所用的事务ID。将这个值和当前事务ID进行比较,可以知道有多少老版本的数据未被清除。这个数字多大才可以安全的取值没有硬性和速成的规定。如果数据没做过任何更新,那么一个巨大的数字也不意味着有未清除的数据,因为实际上所有事务在数据库里査看的都是同一个版本的数据。从另一方面来讲,如果有很多行被更新,那每一行就会有一个或多个版本留在内存里。减少此类开销的最好办法是确保事务一完成就立即将它提交,不要让它长时间地处于打开的状态。因为一个打开的事务即使不做任何操作,也会影响到InnoDB清理旧版本的行数据。
- 同样是在第5行里,还有一项InnoDB清理进程正在使用的撤销日志编号,如果有的话。如果它是“0 0”,如在本例中一样,说明清理进程处于空闲状态。
- 第6行:历史记录的长度,即位于InnoDB数据文件的撤销空间里的页面的数目。如果事务执行了更新并提交,这个数字就会增加;而当清理进程移除旧版本数据时,它就会递减。清理进程也会更新第5行中的数值。
- 第7行:锁结构的数目。每一个锁结构经常持有许多个行锁,所以,它跟被锁定行的数目不一样。
- 头部信息之后就是一个事务列表。当前版本的MySQL还不支持嵌套事务,因此,在某个时间点上,每个客户端连接能拥有的事务数目是有一个上限的,而且每一个事务只能属于单一连接。在输出信息里,每一个事务至少占有两行内容。下面这个例子就是关于一个事务所能看到的最少的信息。
1 ---TRANSACTION 0 3793494, not started, process no 5488, OS thread id 1141152064
2 MySQL thread id 15, query id 479 localhost baron
第1行以该事务的ID和状态开始。这个事务是“not started”,意思是已经提交并且没有再发起影响事务的语句;可能刚好空闲。然后是一些进程和线程信息。第2行显示了MySQL进程ID,也和SHOW FULL PR0CESSLIST中的Id列相同。紧随其后的是一个内部査询号和一些连接信息(同样与SHOW FULL PR0CESSLIST中的相同)。
然而,每个事务会打印比这多得多的信息。下面是一个稍复杂一些的例子。
1 ---TRANSACTION 0 80157600, ACTIVE 4 sec, process no 3396, OS thread id 1148250464, thread declared inside InnoDB 442 2 mysql tables in use 1, locked 0 3 MySQL thread id 8079, query id 728899 localhost baron Sending data 4 select sql_calc_found_rows * from b limit 5 5 Trx read view will not see trx with id>= 0 80157601, sees <0 80157597
本例中的第1行显示此事务已经处于活跃状态4s。可能的状态有“not started”、“active”、 “prepared”和“committed in memory”(一旦被提交到磁盘上,状态就会变为“not started”)。尽管在这个示例里没有显示,但是在其他条件下,你也许能看到关于事务当前正在做什么的信息。在源代码中有超过30个字符串常量可以显示在这里,例如“fetching rows”、“adding foreign keys”,等等。
第1行里的文本“thread declared inside InnoDB 442”的意思是该线程正在InnoDB内核里做一些操作,并且,还有442张“票”可以使用。换句话说,就是同样的SQL査询可以重新进入InnoDB内核442次。这个“票”是系统用来限制内核中线程并发操作的手段,以防止其在某些平台上运行失常。即使线程的状态是“inside InnoDB”,它也不是在InnoDB里面完成所有的工作。査询可能是在服务器一级做一些操作,而只是通过某个途径跟InnoDB内核互动一下。你也可能看到事务的状态是“sleeping before joining InnoDB queue” 或者 “waiting in InnoDB queue”
接下来一行显示了当前语句里有多少表被使用和锁定。InnoDB—般不会锁定表,但对有些语句会锁定。如果MySQL服务器在高于InnoDB层次之上将表锁定,这里也是能够显示出来的。如果事务已经锁定了几行数据,这里将会有一行信息显示出锁定结构的数目(再声明一次,这跟行锁是两回事)和堆的大小。具体例子可以査看之前的死锁输出信息。在MySQL 5.1及更新的版本里,这一行还显示了当前事务持有的行锁的实际数目。
堆的大小指的是为了持有这些行锁而占用的内存大小。InnoDB是用一种特殊的位图表来实现行锁的,从理论上讲,它可将每一个锁定的行表示为一个比特。我们的测试显示,每一个锁通常不超过4比特。
本例中的第3行包含的信息略微多于上例中的第2行:在该行的末尾是线程状态“Sending data”,这跟SHOW FULL PROCESSLIST中所看到的Command列相同。
如果事务正在运行一个査询,那么接下来就会显示出査询的文本(或者,在某些版本的MySQL里,显示其中的一小段),在本例中是第4行。
第5行显示了事务的读视图,它表明了因为版本关系而产生的对于事务可见和不可见两种类型的事务ID的范围。在本例中,在两个数字之间有一个四个事务的间隙,这四个事务可能是不可见的。InnoDB在执行査询时,对于那些事务ID正好在这个间隙的行,还会检查其可见性。
如果事务正在等待一个锁,那么在査询内容之后将可以看到这个锁的信息。在上文的死锁例子里,这样的信息已经看到过多次了。不幸的是,输出信息并没有说出这个锁正被其他哪个事务持有。如果使用了InnoDB插件,就可以在MySQL5.1及更高版本中的INFORMATION-SCHEMA表中查明这一点。
如果输出信息里有很多个事务,InnoDB可能会限制要打印出来的事务数目,以免输出信息增长得太大。这时就会看到“...truncated... ”。
1.2.6 FILE I/O
FILE I/O部分显示的是I/O辅助线程的状态,还有性能计数器的状态。
1 -------- 2 FILE I/O 3 -------- 4 I/O thread 0 state: waiting for i/o request (insert buffer thread) 5 I/O thread 1 state: waiting for i/o request (log thread) 6 I/O thread 2 state: waiting for i/o request (read thread) 7 I/O thread 3 state: waiting for i/o request (write thread) 8 Pending normal aio reads: 0, aio writes: 0, 9 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0 10 Pending flushes (fsync) log: 0; buffer pool: 0 11 17909940 OS file reads, 22088963 OS file writes, 1743764 OS fsyncs 12 0.20 reads/s, 16384 avg bytes/read, 5.00 writes/s, 0.80 fsyncs/s
第4〜7行显示了I/O辅助线程的状态。第8〜10行显示的是每个辅助线程的挂起操作的数目,以及日志和缓冲池线程挂起的fsync()操作数目。简写“aio'的意思是“异步I/O”。第11行显示了读、写和fsync()调用执行的数目。在你的负载下这些绝对值会有所不同,因此更重要的是监控它们过去一段时间内是如何改变的。第12行显示了在头部显示的时间段内的每秒平均值。
在第8〜9行显示的挂起值是检测I/O受限的应用的一个好方法。如果这些I/O大部分有挂起的操作,那么负载可能I/O受限。
在Windows下,可以通过innodb_file_io_threads配置变量来调整I/O辅助线程数,因此可能会看到不止一个读线程和写线程。在使用了InnoDB插件的MySQL5.1和更新版本中,或Percona Server中,可以使用innodb_read_io_threads和innodb_write_io_threads来为读/写配置多个线程。然而,在所有平台下至少总会看到4个线程。
Insert buffer thread
负责插入缓冲合并(例如,记录被从插入缓冲合并到表空间中)。
Log thread
负责异步刷日志。
Read thread
执行预读操作以尝试预先读取InnoDB预感需要的数据。
Write thread
刷脏缓冲。
1.2.7 INSERT BUFFER AND ADAPTIVE HASH INDEX
这部分显示了InnoDB内这两个结构的状态。
1 ------------------------------------- 2 INSERT BUFFER AND ADAPTIVE HASH INDEX 3 ------------------------------------- 4 Ibuf for space 0: size 1, free list len 887, seg size 889, is not empty 5 Ibuf for space 0: size 1, free list len 887, seg size 889, 6 2431891 inserts, 2672643 merged recs, 1059730 merges 7 Hash table size 8850487, used cells 2381348, node heap has 4091 buffer(s) 8 2208.17 hash searches/s, 175.05 non-hash searches/s
第4行显示了关于插入缓存大小、“free list”的长度和段大小的信息。文本“for space 0”像是指明了多个插入缓冲的可能性——每个表空间一个,但从未实现,并且这个文本在最近的MySQL版本中被移除掉了。只有一个插入缓冲,因此第5行真的是多余的。第6行显了有多少缓冲操作已经完成。合并与插入的比例很好地说明了缓冲使用效率如何。
第7行显示了自适应哈希索引的状态。第8行显示了在头部提及的时间内InnoDB完成了多少哈希索引操作。哈希索引査找与非哈希索引査找的比例仅供参考。自适应索引无法配置。
1.2.8 LOG
这部分显示了关于InnoDB事务日志(重做日志)子系统的统计。
1 --- 2 LOG 3 --- 4 Log sequence number 84 3000620880 5 Log flushed up to 84 3000611265 6 Last checkpoint at 84 2939889199 7 0 pending log writes, 0 pending chkp writes 8 14073669 log i/o's done, 10.90 log i/o's/second
第4行显示了当前日志序号,第5行显示了日志已经刷到哪个位置。日志序号就是写到日志文件中的字节数,因此可用来计算日志缓冲中还有多少没有写入到日志文件中。在这个例子中,它有9615字节(13 000 620 880 — 13 000 611 265)。第6行显示了上一检测点(一个检测点表示一个数据和日志文件都处于已知状态的时刻,并且能用于恢复)。如果上一检査点落后日志序号太多,并且差异接近于该日志文件的大小,InnoDB会触发“疯狂刷!”,这对性能而言非常糟糕。第7〜8行显示了挂起的日志操作和统计,你可以将其与FILE I/O部分的值相比较,以了解你的I/O有多少是由日志子系统引起,有多少是其他原因。
1.2.9 BUFFER POOL AND MEMORY
这部分显示了关于InnoDB缓冲池及其如何使用内存的统计。
1 ---------------------- 2 BUFFER POOL AND MEMORY 3 ---------------------- 4 Total memory allocated 4648979546; in additional pool allocated 16773888 5 Buffer pool size 262144 6 Free buffers 0 7 Database pages 258053 8 Modified db pages 37491 9 Pending reads 0 10 Pending writes: LRU 0, flush list 0, single page 0 11 Pages read 57973114, created 251137, written 10761167 12 9.79 reads/s, 0.31 creates/s, 6.00 writes/s 13 Buffer pool hit rate 999 / 1000
第4行显示了由InnoDB分配的总内存,以及其中多少是额外内存池分配。额外内存池仅分配了其中(一般很小)一部分的内存,由内部内存分配器分配。现代的InnoDB版本一般使用操作系统的内存分配器,但老版本使用自己的,这是由于在那个时代有些操作系统并未提供一个非常好的实现。
第5〜8行显示了缓冲池度量值,以页为单位。度量值有总的缓冲池大小、空闲页数、分配用来存储数据库页的页数,以及“脏”数据库页数。InnoDB使用缓冲池中的部分页来对锁、自适应哈希,以及其他系统结构做索引,因此池中的数据库页数永远不等于总的池大小。
第9〜10行显示了挂起的读和写的数量(例如InnoDB需要为缓冲池而做的总的逻辑读和写)。这些值并不与FILE I/O部分的值相匹配,因为InnoDB可能合并许多的逻辑操作到一个物理I/O操作中。LRU代表“最近使用到的”;它是通过冲刷缓冲中不经常使用的页来释放空间以供给经常使用的页的一种方法。冲刷列表存放有检测点处理需要冲刷的旧页,并且单页的写是独立的页面写,不会被合并。
输出中的第8行显示缓冲池包含37 491个脏页,这是在某些时刻(它们已经在内存中被修改但尚未写到磁盘上)需要被刷到磁盘上的。然而,第10行显示当前没有安排冲刷。这不是一个问题;InnoDB会在需要时刷。如果在InnoDB的状态输出中到处可见大量挂起的I/O操作,这往往表明服务器有严重问题。
第11行显示了InnoDB被读取、创建和写入了多少页。读/写页的值指的是从磁盘读到缓冲池中的数据,或反过来说。创建页的值指的是InnoDB在缓冲池中分配但没有从数据文件中读取内容的页,因为它并不关心内容是什么(例如,它们可能属于一个已经被删除的表)。
第13行报告了缓冲池的命中率,它用来衡量InnoDB在缓冲池中査找到所需页的比例。 它度量自上次InnoDB状态输出后的命中率,因此,如果服务器自那以后一直很安静,你将会看到“No buffer pool page ge_ts since the last printout.”。它对于度量缓存池的大小并没有用处。
在MySQL5.5中,可能有多个缓冲池,每一个都会在输出中打印一部分信息。Percona XtraDB还会在输出中打印更多详情——例如,准确显示内存在哪里分配。
1.2.10 ROW OPERATIONS
这部分显示了其他各项InnoDB统计。
1 -------------- 2 ROW OPERATIONS 3 -------------- 4 0 queries inside InnoDB, 0 queries in queue 5 1 read views open inside InnoDB 6 Main thread process no. 10099, id 88021936, state: waiting for server activity 7 Number of rows inserted 143, updated 3000041, deleted 0, read 24865563 8 0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s 9 ---------------------------- 10 END OF INNODB MONITOR OUTPUT 11 ============================
第4行显示了InnoDB内核内有多少线程(我们在讨论TRANSACTIONS部分的小节中提及过)。队列中的査询是InnoDB为限制并发执行的线程量而不允许进入内核的线程。査询同样在进入队列之前会休眠等待,这之前已经讨论过。
第5行显示了有多少打开的InnoDB读视图。读视图是包含事务开始点的数据库内容的MVCC“快照”。你可以看看某特定事务是否在TRANSACTIONS部分有读视图。
第6行显示了内核的主线程状态。可能的状态值如下。
- doing background drop tables
- doing insert buffer merge
- flushing buffer pool pages
- flushing log
- making checkpoint
- purging
- reserving kernel mutex
- sleeping
- suspending
- waiting for buffer pool flush to end
- waiting for server activity
在大部分服务器上应该会经常看到“sleeping”,如果生成多个快照而一再査看到不同的状态,例如“flushing buffer pool pages”,则应该怀疑相关的活动有问题--例如,“疯狂刷”问题,可能由某个冲刷算法差劲的InnoDB版本引起,或由糟糕的配置导致,例如太小的事务日志文件。
第7〜8行显示了多少行被插入、更新、删除和读取,以及它们的每秒均值。如果想査看InnoDB有多少工作在进行,那么它们是很好的参考值。
SHOW ENGINE INNODB STATUS输出在第9〜13行结束。如果看不到这个文本,那可能是有一个大的死锁截断了输出。
1.2.11 SHOW PROCESSLIST
进程列表是当前连接到MySQL的连接或线程的清单。SHOW PROCESSLIST列出了这些线程,以及每个线程的状态信息。例如:
mysql> SHOW FULL PROCESSLIST\G *************************** 1. row *************************** Id: 61539 User: sphinx Host: se02:58392 db: art136 Command: Query Time: 0 State: Sending data Info: SELECT a.id id, a.site_id site_id, unix_timestamp(inserted) AS inserted,forum_id, unix_timestamp(p *************************** 2. row *************************** Id: 65094 User: mailboxer Host: db01:59659 db: link84 Command: Killed Time: 12931 State: end Info: update link84.link_in84 set url_to = replace(replace(url_to,'&','&'),'%20','+'), url_prefix=repl
有几个工具(例如innotop)可以以定期刷新的方式显示进程列表。
也可以从INFORMATION_SCHEMA中的表来获取这个信息。Percona Server和MariaDB向这个表中增加了更多有用的信息,如髙精度的时间字段和显示査询完成百分比的字段,这一信息可用作进度指示。
Command和State列真正表明了线程的状态。上面的例子中,第一个进程正在运行査询并发送数据,而第二个进程已被杀死,这可能是由于这需要非常长的一段时间来完成,于是某人深思熟虑后通过KILL命令终结了它。线程有可能在KILL状态停留一段时间,因为KILL命令有可能不能立刻执行完成,比如它可能需要一些时间来回滚事务。
SHOW FULL PR0CESSLIST (增加了FULL关键字)将显示每个査询的全文,否则最多显示100个字符。
1.2.12 SHOW ENGINE INNODB MUTEX
SHOW ENGINE INNODB MUTEX返回InnoDB互斥体的详细信息,主要对洞悉可扩展性和并发性问题有帮助。每个互斥体都保护着代码中一个临界区,这在之前已经讨论过。
输出会因MySQL版本和编译选项而有所不同。下面是MySQL5.5服务器的示例。
mysql> SHOW ENGINE INNODB MUTEX;
+--------+------------------------------+-------------+
| Type | Name | Status |
+--------+------------------------------+-------------+
| InnoDB | &table->autoinc_mutex | os_waits=1 |
| InnoDB | &table->autoinc_mutex | os_waits=1 |
| InnoDB | &table->autoinc_mutex | os_waits=4 |
| InnoDB | &table->autoinc_mutex | os_waits=1 |
| InnoDB | &table->autoinc_mutex | os_waits=12 |
| InnoDB | &dict_sys->mutex | os_waits=1 |
| InnoDB | &log_sys->mutex | os_waits=12 |
| InnoDB | &fil_system->mutex | os_waits=11 |
| InnoDB | &kernel_mutex | os_waits=1 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=2 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=54 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=1 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=31 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=41 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=12 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=1 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=90 |
| InnoDB | &dict_table_stats_latches[i] | os_waits=1 |
| InnoDB | &dict_operation_lock | os_waits=13 |
| InnoDB | &log_sys->checkpoint_lock | os_waits=66 |
| InnoDB | combined &block->lock | os_waits=2 |
+--------+------------------------------+-------------+
基于等待的数量,可以使用这个输出来帮助确定InnoDB的哪一块是瓶颈。只要有互斥体,就会有潜在的争用。该命令的输出可能会非常多,需要写一些脚本进行聚合分析。
有三种主考的策略可以消除互斥相关的瓶颈:尽量避开InnoDB的弱点,限制并发,或者在CPU密集型的空转等待和资源密集型的操作系统等待之间取得平衡。
2.复制状态
MySQL有几个命令用以监测复制。在主库上执行SHOW MASTER STATUS可显示主库的复制状态和配置。
mysql> SHOW MASTER STATUS\G *************************** 1. row *************************** File: mysql-bin.000079 Position: 13847 Binlog_Do_DB: Binlog_Ignore_DB:
输出包含了主库当前的二进制日志位置。通过SHOW BINARY LOGS可以获取到二进制日志的列表。
mysql> SHOW BINARY LOGS +------------------+-----------+ | Log_name | File_size | +------------------+-----------+ | mysql-bin.000044 | 13677 | ... | mysql-bin.000079 | 13847 | +------------------+-----------+ 36 rows in set (0.18 sec)
要査看这些二进制日志中的事件,可以用SHOW BINLOG EVENTS。在MySQL5.5中,也可以使用SHOW RELAYLOG EVENTS。
在备库上执行SHOW SLAVE STATUS査看复制的状态和配置。在此,我们不予列举,因为输出有点冗长,但我们会说明关于它的几个事情。首先,你可以同时看到复制I/O和复制SQL线程的状态,包括任何错误。也可以看到复制落后多远。输出中还有三套二级制日志的坐标,这几个坐标对于备份和搭备库非常有用。
Master_Log_File/Read_Master_Log_Pos
I/O线程读主库二进制日志的位置。
Relay_Log_File/Relay_Log_Pos
SQL线程执行中继日志的位置。
Relay_Master_Log_File/Exec_Master_Log_Pos
SQL线程执行的映射到主库二进制日志的位置。这与Relay_Log_File/Relay_Log_Pos有着相同的逻辑位置,但是主库的二进制日志而非复制的中继日志。换句话说,如果你看一下日志中的这两个位置,你会发现有相同的日志事件。
2.1 INFORMATION_SCHEMA
INF0RMATI0N_SCHEMA库是一个SQL标准中定义的系统视图的集合。MySQL实现了许多标准中的视图,并且增加了一些其他的视图。在MySQL5.1中,其中许多的视图与MySQL的SHOW命令对应,例如SHOW FULL PR0CESSLIST和SHOW STATUS。然而,也有一些视图并没有相对应的SHOW命令。
INF0RMATI0N_SCHEMA视图的美在于能够以标准的SQL来进行査询。这比SHOW命令更灵活,因为SHOW命令产生的结果不能聚合、联接或进行其他标准SQL操作。在系统视图层拥有所有可获得的数据使得写感兴趣和有用的査询变得可行。
例如,在Sakila样本库中哪一个表引用了actor表? 一致的命名约定使之很容易确定。
mysql> SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS -> WHERE TABLE_SCHEMA='sakila' AND COLUMN_NAME='actor_id' -> AND TABLE_NAME <> 'actor'; +------------+ | TABLE_NAME | +------------+ | actor_info | | film_actor | +------------+
我们需要为本书找几个表中含有多列索引的样例。下面是一个满足需要的査询。
mysql> SELECT TABLE_NAME, GROUP_CONCAT(COLUMN_NAME) -> FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE -> WHERE TABLE_SCHEMA='sakila' -> GROUP BY TABLE_NAME, CONSTRAINT_NAME -> HAVING COUNT(*) > 1; +---------------+--------------------------------------+ | TABLE_NAME | GROUP_CONCAT(COLUMN_NAME) | +---------------+--------------------------------------+ | film_actor | actor_id,film_id | | film_category | film_id,category_id | | rental | customer_id,rental_date,inventory_id | +---------------+--------------------------------------+
你也可以;更复杂的査询,就像对待其他常规表一样。MySQL Forge是一个寻找和分享针对这些视图的査询的好地方。有査找重复和冗余索引,査找非常低基数的索引,以及更多其他的例子。在Shlomi Noach的common_schmea项目中中同样有一组基于INFORMATION_SCHEMA视图所写的有用视图。
最大的缺点是视图与相应的SHOW命令相比,有时非常慢。它们一般会取所有的数据,存在临时表中,然后使査询可以获取临时表。当服务器上数据量大或表非常多时,査询INFORMATION_SCHEMA表会导致非常髙的负载,并且会导致服务器对其他用户而言停转或不可响应,因此在一个高负载且数据量大的生产服务器上使用时要小心。査询时会有危险的表主要是那些包含下列表元数据的表:TABLES,COLUMNS,REFERENTIAL_CONSTRAINTS,KEY_COLUMN_USAGE,等等。对这些表的査询会导致MySQL向存储引擎请求获取类似服务器上表的索引统计等数据,而这在InnoDB里是非常繁重的。
这些视图不可更新。尽管你可以从中检索到服务器设置,但不能更新以影响服务器的配置,因此,仍然需要对配置使用SHOW和SET命令,尽管INFORMATION_SCHEMA视图对其他任务非常有用。
2.1.1 InnoDB表
在MySQL5.1和更新版本中,InnoDB插件创建了许多的INFORMATION_SCHEMA表。这些表非常有用。在MySQL5.5中有更多这样的表,而还未发行的MySQL5.6中则还要多。
在MySQL5.1中,存在如下一些表。
INNODB_CMP和INNODB_CMP_RESET
这些表显示了InnoDB中以新文件格式Barracuda压缩的数据的相关信息。第二个表显示的信息与第一个表相同,但具有重置所包含数据的副作用,好像使用FLUSH命令那样。
INNODB_CMPMEM和INNODB_CMPMEM_RESET
这些表显示了用于InnoDB压缩数据的缓冲池中页的信息。第二个表又是一个重置表。
INN0DB_TRX和INN0DB_L0CKS
这些表显示了事务,拥有和等待锁的事务。它们对于诊断锁等待问题和长时间运行的事务非常重要。MySQL用户手册上包含了査询样例,你可以直接复制、粘贴来显示哪一些事务在阻塞其他事务,它们正在运行的査询,等等。
除了这些表,MySQl5.5还增加了INNODB_LOCK_WAITS,它可以帮助更容易地诊断更多类型的锁等待问题。MySQL5.6中将会增加显示关于InnoDB内部更多信息(包括缓冲池和数据字典)的表,以及称为INNODB_METRICS的新表,它将是使用Performance Schema的替代方案。
2.1.2 Percona Server 中的表
Percona Server向INFORMATION_SCHEMA库中增加了大量的表。原生的MySQL5.5服务器有39个表,而Percona Server5.5有61个表。以下是关于新增表的概述。
“用户统计信息”表
这些表源于Google的MySQL补丁。它们显示了客户端、索引、表、线程和用户的活动统计。之前提高过使用方式,例如确定复制何时开始接近追赶上主库的能力极限。
InnoDB数据字典
一系列的表以只读表的方式暴露了InnoDB内部数据词典:列、外键、索引、统计,等等。它们对从InnoDB角度检测和理解数据库非常有帮助,它可能与MySQL不同,因为MySQL依赖于.frm文件来存储数据字典。类似的表在MySQL5.6发行时会加进来。
InnoDB缓冲池
这些表使你可以像表一样査询缓冲池,表中每个页是一行,因此,你可以看到什么页驻存于缓冲池中,有哪种类型的页,等等。这些表已被证实对于诊断类似膨胀的插入缓冲非常有用。
临时表
这些表显示了与INFORMATION_SCHEMA.TABLES表中可获取的类型相同的信息,只是用临时表取代了。有一个用于你自身会话的临时表,还有一个用于整个服务器中的所有临时表。它们对某个会话获取可视性到存在的临时表中,以及它们使用了多少空间。
杂项表
有少数其他表为査询执行时间、文件、表空间和更多InnoDB内部信息增加了可视性。
2.2 Performance Schema
自MySQL5.5 起,Performance Schema (寄存于PERFORMANCE_SCHEMA库中)是MySQL增强仪表的新的汇总处。
默认情况下,Performance Schema是禁掉的,你必须打开并且使其在一个想要收集的特定的仪表点(“消费者”)启用。我们对服务器以几个不同的配置做了基准测试,发现即使Performance Schema没有数据可采集也会导致8%〜11%的开销,并且所有消费都生效的话会有19%〜25%的开销,具体取决于是一个只读还是读/写的负载。这算少还是多由你来决定。
这在MySQL5.6中将改善,特别是当特性本身生效但所有仪表点都禁用时。这对某些用户而言更加实用,他们会让Performance Schema生效,但直到收集信息时才将其激活。
在MySQL5.5中,Performance Schema包含了指示条件变量、互斥体、读/写锁和文件I/O实例的表。还有指示实例上的等待信息的表,而这些经常是你在査询时首先感兴趣的,以及与其实例表的联接。这些事件等待表有几种变体,拥有关于服务器性能和行为的当前和历史信息。最后,还有一组“设置表”,你可以用这些表来使预想的消费者生效或失效。
在MySQL5.6.3开发里程碑的第6个发行中,Performance Schema中的表数从17增长到了 49。这意味着MySQL5.6中有许多的仪表!增加的仪表涵盖SQL语句、语句过程(基本上与你在SHOW PROCESSLIST中看到的线程状态相同)、表、索引、主机、线程、用户、账号,以及各种总述及历史表等。
你如何使用这些表?有49个表,得让某些人为此写些工具来帮助大家了。然而,对于与Performance Schema表相对应的早期流行的非常不错的SQL例子,可以阅读Oracle工程师Mark Leith的博客上的一些文章,例如 http://www.markleith.co.uk/?p=471.
3.总结
MySQL暴露服务器内部信息的首要方式是SHOW命令,但这在改变。在MySQL5.1中引入的可插拔的INFORMATION_SCHEMA表允许InnoDB插件增加一些非常有意义的仪表,而Percona Server增加的要多得多。然而,读取SHOW ENGINE INNODB STATUS输出并解释的能力对管理InnoDB仍然是至关重要的。在MySQL5.5和更新的服务器版本中,可以使用Performance Schema,它将来可能变成深入服务器内部最强大和完备的方式。Performance Schema最棒的一点在于它是基于时间的,这意味着MySQL最终可以获取已经逝去时间里的仪表盘,而不仅仅是已操作的次数。