MySQL服务无法启动的问题有可能是:
/A/ 第一反应
A/1 初始步骤
A/2 如果 MySQL崩溃
/B/ 识别问题
B/1 检查日志
B/1.1 页面损坏
B/1.2 InnoDB 时间旅行和日志序列号错误
B/1.3 数据字典错误
B/2 检查表的错误
B/2.1 使用 CHECK TABLE / mysqlcheck
B/2.2 使用innochecksum
/C/ 恢复你的数据
C/1 MySQL Utilities / 从 .frm 文件中提取 CREATE TABLE 语句
C/1.1 下载和安装 MySQL Utilities
C/1.2 从 .frm 文件中提取 CREATE TABLE 语句
C/2 损坏的表
C/2.1 使用 CREATE .. LIKE 恢复表
C/2.2 恢复多个/所有 InnoDB 数据库并重新创建 ibdata/ib_log 文件
C/3 日志序列号不匹配/将来
C/3.1 删除和重新创建数据
C/3.2 重新创建 ib_logfiles
C/3.3 执行引擎交换
C/4 数据字典问题
C/4.1 如何正确删除 .ibd 文件
C/4.2 .ibd 文件丢失后重新创建表
C/4.3 .frm 文件存在但表不存在
C/4.4 孤立表或缺少 .frm 文件
以下是详细的解决办法:
/A/ 第一反应
A/1 初始步骤 - 停止、备份、重新启动
1.停止MySQL 服务器。如果它已经离线或正在崩溃,请跳到第 2 步。
代码:
/scripts/restartsrv_mysql --stop
这里的目标是冻结数据和表文件的当前状态,这样就不会发生新的写入,并且我们可以制作文件副本,而不用担心会导致数据不一致或存储信息丢失的更改。
2.备份您的数据和日志文件,如果不是整个 MySQL 数据目录。
代码:
mkdir /root/innodb.bak (or backup path of your choice)cd /var/lib/mysql (or alternate data directory, if configured)dd if=ibdata1 of=ibdata1.bak conv=noerrorcp -p ./ibdata* /root/innodb.bak/cp -p ./ib_log* /root/innodb.bak/
首先,您要创建一个目录来放置任何文件副本,然后,您要在 /var/lib/mysql(或您的数据目录)中创建 ibdata1 文件的本地备份,以及 ibdata 的备份和 ib_logfiles 进入您的备份目录。我喜欢同时使用 dd 和 cp 来制作 ibdata 文件的副本,因为这两个实用程序之间的性质不同。dd 使用程序复制原始文件,而 cp 将文件的内容复制到新文件。我没有经历过任何特殊情况,这对恢复成功至关重要,但我认为这仍然是我的一个习惯,我怀疑这可能不是一个坏习惯。
理想情况下,特别是如果您还没有备份,那么此时您可能会想要尝试对您的数据目录进行完整的复制。
代码:
cp -Rp /var/lib/mysql{,.orig}
我意识到这对于紧急情况下的某些人来说可能过于耗时或不切实际,所以如果这不可行,那没关系 - 数据文件和 InnoDB 数据库目录至少应该提供一些可以在需要时依赖的东西。
3.备份你的 InnoDB 数据库文件夹
假设您尚未备份完整的 MySQL 数据目录,您仍然需要确保包含 InnoDB 表的任何数据库也备份了各自的文件夹。如果您不确定您的哪些数据库包含 InnoDB 表,您可以使用类似这样的命令来检查包含 .ibd 文件的目录并将它们复制到您的备份文件夹(本示例中为 /root/innodb.bak - 另外,如果您的 DATADIR 不是默认值,则需要在开始时更新变量):
代码:
DATADIR=/var/lib/mysql; find $DATADIR -type f -name *.ibd | awk -F/ '{print $(NF-1)}' | sort | uniq | xargs -I {} cp -Rp $DATADIR/{} /root/innodb.bak
4.启动MySQL 服务器(如果可以的话)
此时,可以安全地使 MySQL 重新联机,前提是您可以这样做而不会导致崩溃。如果您可以将其联机,请继续并启动 MySQL 服务,然后执行 mysqldump - 我建议执行以下操作(如果您愿意,可以将这些转储到 /root 以外的其他路径 - 请记住您选择的内容):
代码:
/etc/init.d/mysql startmysqldump --single-transaction -AER > /root/dump_wtrans.sqlmysqldump -AER > /root/dump.sql
使用单事务标志转储它会在单个事务中创建转储,这可以防止锁定数据库,并且如果您正在运行 100% InnoDB 环境可能会有所帮助 - 所以为了安全起见,特别是如果您是不确定,我建议同时运行。
请务必检查您的 SQL 转储内容以确保数据确实存在。在某些情况下,如果由于某种原因无法访问数据,则只会出现表结构。如果您操作的数据库经常运行 ALTER TABLE 命令,则在使用 电脑 --single-transaction 时尤其如此。如果 mysqldump 与特定表上的 ALTER TABLE 一致,则有可能只存在该结构。(在 MySQL 错误报告 # 71017中详细讨论)
注意:如果您正在处理文件系统损坏,请尝试将这些文件备份到另一个磁盘驱动器(如果可用)(或者甚至备份到安全的远程主机,如果可行)
A/2 如果 MySQL 崩溃
如果 MySQL 有崩溃,并拒绝重新启动,那么这可能是您目前主要关心的问题。当然,您希望它在线用于生产目的,但最重要的是,让 MySQL 在线可以让您获得数据的真实 MySQL 转储,这样您就可以最大限度地减少永久丢失任何数据的机会,并帮助修复可能存在的表损坏。
因为 InnoDB 的 ACID 合规性(MySQL :: MySQL 5.6 Reference Manual :: 14.2.1 MySQL and the ACID Model),它遵守严格的数据一致性标准。这实质上意味着,如果它遇到任何数据问题,它几乎总是会使 MySQL 崩溃,以防止进一步的一致性问题。从理论上讲,这是一件好事,但在实践中,计划外停机绝不是一件好事。
然而,使用 innodb_force_recovery 选项通常可以帮助至少让 MySQL 回到可访问状态。也就是说,最好知道它为什么有效,以及如何小心使用它。
使用 innodb_force_recovery
默认情况下,InnoDB 已经尝试基本恢复步骤,当它发现问题时,但通常情况下,您需要添加innodb_force_recovery在您的 /etc/my.cnf 文件中设置以帮助它。这指示 InnoDB 以恢复模式启动,告诉它跳过 InnoDB 启动过程的各个部分,这通常是发生崩溃的地方。您需要从最低值 1 开始,然后仅根据需要增加它,最高可能值为 6。此设置在 my.cnf 电脑 文件的 [mysqld] 部分下输入,如本示例所示:
代码:
[mysqld]innodb_force_recovery = 1
您还可以运行以下单行命令将其自动添加到您的 /etc/my.cnf 文件中的正确部分下(将开头的“mode =”变量中的数字更改为您想要使用的任何模式):
代码:
mode=1; sed -i "/^\[mysqld\]/{N;s/$/\ninnodb_force_recovery=$mode/}" /etc/my.cnf
然后,一旦您准备好将服务器恢复到默认模式,您可以使用以下命令删除 innodb_force_recovery 行:
代码:
sed -i '/innodb_force_recovery/d' /etc/my.cnf
此配置选项不应用作保持服务器在线的长期甚至中期解决方案。如果您的服务器只能在启用 innodb_force_recovery 的情况下保持在线,那么您的服务器上仍然存在需要解决的主要问题。如果 innodb_force_recovery 长时间处于活动状态,您可能会在服务器上创建更多问题,特别是如果设置为高值(没有充分的理由将 innodb_force_recovery 长时间设置为 6)。此模式完全是临时的 - 仅用于恢复目的。
以下是每种模式的作用的简要概述(每种模式也会自行组合,这意味着较高的值包含所有较低值的特征):
模式 1 - 当看到损坏的页面时不会使 MySQL 崩溃模式 2 - 不运行后台操作模式 3 - 不尝试回滚事务模式 4 - 不计算统计数据或应用存储/缓冲的更改模式 5 - 启动期间不查看撤消日志模式 6 - 启动期间不从重做日志 (ib_logfiles) 前滚例如:如果您的 MySQL 服务器以模式 3 启动,而不是模式 2,则可以安全地假设崩溃与事务回滚过程有关。另外,请注意,从 MySQL 5.6.15 开始,模式 4-6 会将 MySQL 置于只读模式。
如果您已经经历了所有的 innodb_force_recovery 模式,并且您仍然因 InnoDB 错误而崩溃,那么下一个最佳举措是尝试收集一些有关可能导致崩溃的额外信息
。/B/ 识别问题
InnoDB 问题可以通过多种不同的方式弹出,虽然笼统的术语“腐败”通常用于涵盖很多问题——通常是不准确的——但尝试具体确定你的问题通常是个好主意处理。
B/1 检查日志
如果您怀疑 InnoDB 表或数据库已损坏,可能是因为您注意到数据损坏、不存在的数据或拒绝启动的 MySQL 服务。对于任何这些情况,您首先要查看的是 MySQL 错误日志。在典型设置中,这将位于 /var/lib/mysql/ 中,该文件将是您的主机名,带有 .err 后缀。如果您不知道您的主机名,或者不想全部输入(如果不是默认值,请将数据目录替换为您自己的),这里有一个快速命令可以提取日志中的最后 200 行:
代码:
tail -200 /var/lib/mysql/`hostname`.err
这将执行主机名命令,并使用返回的字符串代替“主机名”,这是命令行中反引号的功能。
您可能会在此处看到一些可以帮助您确定您遇到的腐败类型(如果有)的内容。在本指南中,我将介绍您将遇到的三种最常见的与损坏相关的问题——页面损坏、日志序列号问题和数据字典问题。以下是一些示例,并解释了它们的含义:
B/1.1 页面损坏代码:InnoDB: Database page corruption on disk or a failedInnoDB: file read of page 515891.这通常会在之前提供更多信息,您应该注意这些信息,因为它可能包含一些有关此损坏具体发生在何处的有用信息,但最终这会告诉您 InnoDB 似乎认为您有页面引用的页面 ID 上存在损坏,或者可能只是无法以一种或另一种方式读取文件。这不一定表明真正损坏的证据,事实上,在某些情况下,这可能只是操作系统损坏其自己的文件缓存的结果。因此,通常建议尝试重新启动计算机 - 当然是在进行备份之后 - 然后再继续进行任何进一步的操作。也就是说 - 如果重新启动确实解决了您的问题,您可能需要确保您的 RAM 没有缺陷或即将淘汰,因为这可能是操作系统损坏其自己的文件缓存的常见原因之一。在尝试任何恢复之前,您可能希望解决这种情况,以避免立即遇到同样问题的风险。如果您不确定,或者您重新启动后仍然怀疑存在损坏,您可以运行以下脚本来执行对所有 .ibd 文件进行innochecksum以尝试识别损坏。如果 MySQL 仍然无法成功启动,这将特别有用,因为它直接在文件上运行而无需 MySQL 访问(事实上,如果它正在检查的表空间在服务器上打开,它将无法工作):代码:#!/bin/bash
for i in $(ls /var/lib/mysql/*/*.ibd)
do
innochecksum $i
doneinnochecksum 实用程序查看表空间文件中的页面,并计算每个页面的校验和。然后,它将这些中的每一个与存储的校验和进行比较,并让您知道是否存在不匹配。如果是这样,那通常表明页面已以某种方式损坏。如果没有发现不匹配,它将不会显示任何输出(除非包含 -v 用于详细输出)。如果 MySQL 在线且可访问,您始终可以使用 CHECK TABLE 语句,如下所述:MySQL :: MySQL 5.5 Reference Manual :: 13.7.2.2 CHECK TABLE SyntaxB/1.2 InnoDB 时间旅行和日志序列号错误代码:mysql: 120901 9:43:55 InnoDB: Error: page 70944 log sequence number 8 1483471899
mysql: InnoDB: is in the future! Current system log sequence number 5 612394935.
mysql: InnoDB: Your database may be corrupt or you may have copied the InnoDB
mysql: InnoDB: tablespace but not the InnoDB log files. See
mysql: InnoDB: [url=http://dev.mysql.com/doc/refman/5.5/en/forcing-innodb-recovery.html]MySQL :: MySQL 5.5 Reference Manual :: 14.21.2 Starting InnoDB on a Corrupted Database[/url]首先,解释一下究竟什么是日志序列号 (LSN)。对于 InnoDB 引擎上发生的每个操作,记录都会写入“重做”日志文件,默认情况下,通常在 MySQL 数据目录中被视为 ib_logfile0 和 ib_logfile1。这两个文件有一个固定的大小(在 MySQL 5.6.8+ 中,每个文件默认为 48M),记录按顺序写入这些文件,从第一个日志文件开始直到它到达末尾,然后继续第二个日志文件。一旦它到达第二个日志文件的末尾(假设只配置了 2 个默认日志文件 - 请参阅innodb_log_files_in_group),它会重新开始并再次开始在第一个日志文件的开头写入。这些记录中的每一个都被赋予一个关联的 LSN。此外,当数据库被修改时,该数据库中的特定页面也会被赋予一个关联的 LSN。在这两者之间,这些 LSN 会一起检查,以确保以正确的顺序执行操作。LSN 本身本质上是日志文件的偏移量,存储在数据库页面标题中的 LSN 告诉 InnoDB 需要刷新多少日志。在某个地方,无论是意外重启、内存问题、文件系统损坏、复制问题、手动更改 InnoDB 文件还是其他原因,这些 LSN 都变得“不同步”。无论它是否使您的服务器崩溃,这都应该被视为合法的损坏,并且通常是您想要解决的问题B/1.3 数据字典错误代码:[ERROR] Table ./database/table has no primary key in InnoDB data dictionary, but has one in MySQL!
InnoDB: Error: table 'database/table'
InnoDB: in InnoDB data dictionary has tablespace id 423,
InnoDB: but tablespace with that id or name does not exist. Have
InnoDB: you deleted or moved .ibd files?
[ERROR] Cannot find or open table database/table from
the internal data dictionary of InnoDB though the .frm file for the
table exists. Maybe you have deleted and recreated InnoDB data
files but have forgotten to delete the corresponding .frm files
of InnoDB tables, or you have moved .frm files to another database?
or, the table contains indexes that this version of the engine
doesn't support.稍微解释一下 InnoDB 的数据字典,它存在于系统表空间中——它本身作为存储在 ibdata1 文件中的一组特殊页面存在(系统表空间将始终被引用为“空间 0”)——并存储任何表的元数据InnoDB 显式处理的、列或索引。这不是结构元素的主要位置——它们位于每个 InnoDB 表的.frm文件中——但是,它确实包含许多相同的信息。这是您通常会看到导致这些错误的差异的地方。如果出于任何原因更改、移动、手动更改或替换了 ibdata1 文件 - 您会突然得到一个数据字典,它不能反映文件或数据库结构中存在的内容。如果您已经阅读了前面的错误描述,此时您应该明白,存在于 ibdata1(或以其他方式命名)文件中的数据与存在于各个表空间/.ibd/.frm 中的数据之间存在明显的关联文件。当这种关联丢失或切断时,可能会发生不好的事情。因此,您会看到出现此类数据字典错误的最常见原因是因为某些内容被移动或手动更改。它通常归结为:“数据字典希望此文件或表空间在此处,但它不是!”,或“.ibd/.frm 文件希望此项目在数据字典中,但它不是! ”。再次记住,数据字典存储在 ibdata 文件中,在大多数环境中,它只是存储在 MySQL 数据目录中的 ibdata1。
B/2 检查表的错误
日志通常是问题发生时的直接指示,但有时它们可能有点模糊。通常,您会看到损坏的迹象,但不知道哪些表、页面或数据库受到影响。检查表的两种与 InnoDB 相关的方法是CHECK TABLE SQL 语句和innochecksum实用程序。您在此处使用的方法将取决于一个因素:您的 MySQL 服务器是在线还是离线。
MySQL 正在运行且可访问?使用检查表。innochecksum 不检查服务器当前打开的表空间。MySQL 已崩溃或以其他方式脱机?innochecksum是这里的方法 - 它查看表空间文件中的页面,计算每个页面的校验和,并将其与存储的校验和值进行比较。如果这些不匹配,MySQL 将崩溃,并且损坏或数据修改以某种方式显而易见,因此这可能是确认表空间中的合法问题的可靠方法。B/2.1 使用 CHECK TABLE / mysqlcheck
CHECK TABLE 命令,也被mysqlcheck实用程序使用(特别是 -c 标志,但是 mysqlcheck 默认为这种行为),通过许多不同的确认和比较检查来尝试和识别腐败的迹象。CHECK TABLE 和 mysqlcheck 都适用于 MyISAM和InnoDB 表,但是——在本文的上下文中——我将重点关注它对 InnoDB 表的作用。
请注意,mysqlcheck -r 的 REPAIR 功能和“REPAIR TABLE” MySQL 命令在 InnoDB 表上不起作用;mysqlcheck 主要仅在这种情况下用于识别问题 - 而不是解决问题。
以下是它在内部专门寻找的内容的细分:
存在相应的 .ibd 表空间文件。主索引一致性正确的顺序(按键升序)唯一约束完好无损索引条目数对表中的所有其他索引重复步骤 1-5。最后,所有表都经过自适应哈希索引检查。如果其中任何一个返回不正确或不一致的值,则该表可能被标记为已损坏。一旦表被标记为损坏,在问题得到解决或后续表检查能够确认问题不再存在之前,不能再使用该表。
在某些情况下,如果 CHECK TABLE 例程在 MySQL 自己遇到问题之前发现了 InnoDB 表的问题,这实际上可能会导致 MySQL 服务器被关闭以避免导致额外的错误。虽然这可能是一件好事,因为它可以帮助您阻止任何进一步的损害发生,但当您决定在 InnoDB 表上运行 CHECK TABLE 或 mysqlcheck 时,意识到这一点总是好的。
这不是发现的问题是简单的损坏或错误的情况。发现的损坏/错误只会导致相应地标记索引/表。
运行 CHECK TABLE
CHECK TABLE 作为命令必须在 MySQL shell 中运行,或者通过 MySQL 在其他地方执行。例如,这是我通过将现有的 dictionary.ibd 文件替换为来自同一数据库的另一个表的 .idb 文件而创建的一种情况,您可以在其中看到在普通表上执行的 CHECK TABLE 与已损坏或已损坏的表之间的比较遇到错误:
代码:
mysql> CHECK TABLE roundcube.users;+-----------------+-------+----------+----------+| Table | Op | Msg_type | Msg_text |+-----------------+-------+----------+----------+| roundcube.users | check | status | OK |+-----------------+-------+----------+----------+1 row in set (0.13 sec)mysql> CHECK TABLE roundcube.dictionary;+----------------------+-------+----------+----------------------------------------------------------------+| Table | Op | Msg_type | Msg_text |+----------------------+-------+----------+----------------------------------------------------------------+| roundcube.dictionary | check | Warning | InnoDB: Tablespace is missing for table 'roundcube/dictionary' || roundcube.dictionary | check | Error | Table 'roundcube.dictionary' doesn't exist || roundcube.dictionary | check | status | Operation failed |+----------------------+-------+----------+----------------------------------------------------------------+3 rows in set (0.00 sec)
在这种情况下,遇到的错误实际上保证了服务器会自动关闭。我第一次在 roundcube.dictionary 上运行 CHECK TABLE 的那一刻,服务器崩溃了。这是因为我将活动的 MySQL 实例“引入”了问题的存在。
InnoDB 的数据一致性合规性坚持认为,一旦发现此类问题,就立即停止。根据确切触发崩溃的原因,需要不同级别的 innodb_force_recovery 才能恢复 MySQL 服务器。在缺少表空间的情况下,最小值 - 1 - 可以正常工作。
请记住,MySQL 服务器因某种原因而被强制崩溃. 不要通过快速启用 innodb_force_recovery 直接跳回 MySQL!这有可能,有时甚至是确定性,会导致比它解决的问题更多的问题。
运行 mysqlcheck
通常,您需要一次检查多个表或数据库。CHECK TABLE 不支持通配符,因此在检查数据库中的所有表或检查服务器上的所有数据库时使用起来很不方便。mysqlcheck - 默认情况下从命令行执行 CHECK TABLE - 通过允许您轻松检查整个数据库或所有数据库来弥补这一点。在数据库上执行 CHECK TABLE 的默认语法是(将 db_name 替换为您的数据库名称):
代码:
mysqlcheck db_name
然后它将输出对该数据库中每个表执行 CHECK TABLE 的结果。如果您只想在数据库中的几个选择表上执行它,您还可以在数据库名称之后指定表 (mysqlcheck db_name tbl1_name tbl2_name...)。
这是我在前一个场景中使用的圆形立方体数据库上执行的示例:
代码:
-bash-4.1# mysqlcheck roundcuberoundcube.cache OKroundcube.cache_index OKroundcube.cache_messages OKroundcube.cache_thread OKroundcube.contactgroupmembers OKroundcube.contactgroups OKroundcube.contacts OKroundcube.cp_schema_version OKroundcube.dictionaryWarning : InnoDB: Tablespace is missing for table 'roundcube/dictionary'Error : Table 'roundcube.dictionary' doesn't existstatus : Operation failedroundcube.identitiesWarning : InnoDB: Tablespace is missing for table 'roundcube/identities'Error : Table 'roundcube.identities' doesn't existstatus : Operation failedroundcube.searches OKroundcube.session OKroundcube.system OKroundcube.users OK
此外,您可以使用 -A 标志(或 --all-databases)对服务器上所有数据库中的所有表执行 CHECK TABLE。
B/2.2 使用innochecksum
如前所述,InnoDB 需要有一致的数据,当它自己遇到校验和不匹配时,它会立即停止一个活动的服务器。考虑到这一点,innochecksum 不仅可以帮助在事后识别损坏,而且可以帮助您总体上关注您的校验和状态。在预防的上下文中,这里涉及的唯一缺点是它不能在任何打开的表空间文件上运行。因此,为了获得有关表校验和状态的任何体面图片,需要使服务器脱机。
但是,因为我们正在处理的是崩溃的 MySQL 服务器上的损坏,所以这可能是您最不担心的问题。innochecksum 在这些情况下非常适合跟踪不匹配的校验和,特别是因为它不需要服务器在线。
您从 innochecksum 获得的输出将根据发生的情况而有所不同,并且通常 - 除非您使用 -v 指定详细输出 - 除非实际发现问题,否则您将看不到任何输出。这是在数据文件中发现的校验和失败的示例:
代码:
page 8 invalid (fails old style checksum)page 8: old style: calculated = 0x 8195646B; recorded = 0x DA79A2EE
innochecksum 实用程序目前仅对特别引用的表空间文件 (.ibd) 进行操作,但您可以轻松地使用如下的 find 命令对所有 .ibd 文件执行 innochecksum(适当调整 DATADIR):
代码:
DATADIR=/var/lib/mysql; find $DATADIR -type f -name *.ibd -o -name ibdata* | xargs -I{} innochecksum {}
/C/ 恢复您的数据
一旦您确定了问题并适当地准备了您的服务器,您的下一步就是让您的数据恢复正常工作。MySQL此时应该在线并且至少部分响应,无论是使用innodb_force_recovery还是其他方式。
C/1 MySQL 实用程序 / 从 .frm 文件中提取 CREATE TABLE 语句
MySQL 提供了一组可下载的实用程序,其中包括一些可能有助于恢复过程的工具——特别是,包括一个名为“mysqlfrm”的实用程序。此实用程序可以相当容易地从 .frm 文件中提取表的 CREATE TABLE 语句。这种说法可以极很有用,因为几乎所有有用的恢复方法都涉及能够重新创建您尝试修复的原始表的结构,而且通常,这必须在没有任何直接 MySQL 访问原始表本身的情况下完成。
C/1.1 下载和安装 MySQL 实用程序:
在此处下载软件包。将其提取到您的服务器中的某处代码:tar xvzf mysql-utilities*切换到解压后的目录,赋予 setup.py 执行权限,并运行它的构建和安装操作代码:cd mysql-utilities-1.4.3chmod +x setup.py
./setup.py build
./setup.py install
C/1.2 从 .frm 文件中提取 CREATE TABLE 语句:
mysqlfrm将根据您现有的安装创建自己的临时 MySQL 守护程序,这意味着如果您现有的 MySQL 安装已经在运行,您需要指定一个备用端口。这是我运行以从“staff.frm”文件中提取 CREATE TABLE 的示例:
代码:
mysqlfrm --basedir=/usr --user=mysql --port=3308 /var/lib/mysql/testdb/staff.frm
这是随后的输出:
代码:
# Spawning server with --user=mysql.# Starting the spawned server on port 3308 ... done.# Reading .frm files## Reading the staff.frm file.## CREATE statement for staff.frm:#CREATE TABLE `staff` ( `staff_id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT, `first_name` varchar(45) NOT NULL, `last_name` varchar(45) NOT NULL, `address_id` smallint(5) unsigned NOT NULL, `picture` blob, `email` varchar(50) DEFAULT NULL, `store_id` tinyint(3) unsigned NOT NULL, `active` tinyint(1) NOT NULL DEFAULT '1', `username` varchar(16) NOT NULL, `password` varchar(40) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`staff_id`), KEY `idx_fk_store_id` (`store_id`), KEY `idx_fk_address_id` (`address_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8#...done.
从“CREATE TABLE”部分到“CHARSET=utf8”的上述输出中的所有内容都是完整的、可执行的 CREATE TABLE 语句,它是重新创建具有正确结构的“staff”表所需的。要在 MySQL shell 中执行此操作,我只需粘贴该完整语句,并用分号 ( 结尾
。在某些情况下,您还很可能需要禁用外键检查才能成功:
代码:
SET FOREIGN_KEY_CHECKS=0;
C/2 损坏的表
如果您确定服务器中当前存在损坏的表,您可以在此处采取一些方法,具体取决于严重程度。在几乎所有表损坏的情况下,您至少需要在 innodb_force_recovery 设置为 1 的情况下运行 InnoDB,以允许 MySQL 在您使用它时保持在线。
C/2.1 使用 CREATE .. LIKE 恢复表
此方法的目标是尝试并使用表的现有结构和数据,假设它们是可访问的,以简单地创建具有相同结构和数据的新表,以达到目的更换原件。基本步骤如下:
只需运行以下命令即可访问 MySQL shell:代码:mysql这引用了 /root/.my.cnf 文件以获取凭据。如果它们是准确的,您将不需要从根 shell 提供凭据。否则,您需要手动提供:代码:mysql -u root -p成功后,您应该会看到这样的提示:代码:mysql>运行以下 MySQL 语句,分别将 tablename 和 dbname 替换为您的表和数据库的名称:代码:USE dbname;CREATE TABLE tablename_recovered LIKE tablename;
INSERT INTO tablename_recovered SELECT * FROM tablename;如果您在这里没有遇到任何问题,那么您可能很幸运。此时,您可以删除原始表,并将“_recovered”表名称更改回原始表:代码:DROP dbname.tablename;
RENAME TABLE dbname.tablename_recovered TO dbname.tablename;
当涉及页面损坏时,此方法是最简单的,但可能成功率最低,因为它依赖于您能够从功能上选择所有数据并根据其结构创建恢复表。如果其中任何一个无法访问或以其他方式无法读取,则此方法可能会失败。
但是,如果它在其中涉及增量插入的“SELECT *”部分失败,您确实有另一个选择。因此,您不必执行上面显示的“INSERT INTO ... SELECT * ...”,而是执行以下操作:
代码:
insert ignore into tablename_recovered select * from tablename limit 10;insert ignore into tablename_recovered select * from tablename limit 50;insert ignore into tablename_recovered select * from tablename limit 100;insert ignore into tablename_recovered select * from tablename limit 200;...
使用这种方法,您可以拼凑可访问的数据,直到您到达故障点,此时您可能会失去与 MySQL 服务器的连接。
C/2.2 恢复多个/所有 InnoDB 数据库并重新创建 ibdata/ib_log 文件
此方法的成功将再次取决于 mysqldump 从每个有问题的表中生成功能数据集的能力,但通常更全面的方法,因为它涉及初始化新的 ibdata 和 ib_log 文件。然而,正因为如此,如果不采取谨慎措施,这种方法也很有可能以失败告终。确保您已经完成了 First Response 中的步骤,并且在继续执行此操作之前,您有一组单独的备份。
如果您已经有要在现有损坏的数据库上恢复的备份转储,您也可以使用此方法 - 只需从第 2 步开始。
执行所有数据库的 mysqldump。代码:mysqldump -AER > /root/recovery_dump.sql如果您在此处遇到任何错误,请停下来仔细查看错误。如果它们表明您的任何重要数据已损坏到无法正确转储的程度,您可能不想继续使用此方法。此外,请务必查看生成的转储文件,以确保它实际上包含预期的数据。删除所有受影响的 InnoDB 数据库。代码:mysql> SET FOREIGN_KEY_CHECKS=0;mysql> DROP DATABASE db1;
mysql> DROP DATABASE db2;
...在禁用 innodb_fast_shutdown 后停止 mysqld,以确保执行干净、完全的关闭。代码:mysql -e "SET GLOBAL innodb_fast_shutdown = 0"
/etc/init.d/mysql stop重定位 InnoDB 数据和重做日志文件代码:mv /var/lib/mysql/ibdata* /tmp/
mv /var/lib/mysql/ib_log* /tmp/注释掉或删除您当前在 /etc/my.cnf 中的任何 innodb_force_recovery 条目。代码:sed -i '/innodb_force_recovery/d' /etc/my.cnf启动 mysqld 并监控日志以确保它上线并适当地初始化数据和重做日志文件代码:nohup /etc/init.d/mysql start & tail -f /var/lib/mysql/`hostname`.err恢复转储,一旦您确信 MySQL 仍然功能在线并准备好导入数据。代码:mysql < /root/recovery_dump.sql
C/3 日志序列号不匹配/将来
作为一种尝试确保您的数据保持一致并允许相当于“撤消/重做”功能的方式,InnoDB 在日志中维护所谓的日志序列号文件和表空间文件。每次对 InnoDB 表中的任何数据进行更改时,该更改都会导致日志序列号更新。这相当于一个偏移量,它指示 InnoDB 在文件中向前或向后看多远,以便引用特定的数据状态。
如果在任何时间点发生的事情导致一个更新,而不是另一个,你最终会在你的 MySQL 日志中看到关于“不匹配的日志序列号”的错误,或者“日志序列号在未来”。重要的是让这些回到正轨,以便您的数据库服务器再次正常运行
。C/3.1 删除和重新创建数据
这是最有效也是唯一“真实”的解决方案,但不幸的是,对于许多人来说,它是最不适用的,因为在现实世界的情况下,并不是每个人都能轻松获得这些数据(尽管如果你现在正在经历这种体验,它可能是考虑建立有效备份解决方案的好时机)。但是,如果您的 MySQL 实例没有崩溃,并且您能够创建一个 mysqldump,那么尝试重新导入它当然值得一试。您可以按照“恢复多个/所有 InnoDB”中详细说明的步骤进行操作Databases and Re-create ibdata/ib_log files" 方法在上述 Corrupted Tables 部分下尝试恢复现有数据库的转储
同样,在此处进行任何更改之前,请确保您已经复制了所有重要的 ibdata、ib_logfile、.ibd 和 .frm 文件。
C/3.2 重新创建 ib_logfiles
如果您只处理单个 MySQL 实例,而不是 master->slave 或其他集群情况,这可能是一种有效的尝试方法。这里的目标是将现有的 ib_logfiles 排除在外,允许 MySQL 在重新启动时重新初始化它们。老实说,我用这种方法取得的成功相当有限,但它已经工作了足够多的时间,值得一提:
代码:
mysql -e "SET GLOBAL innodb_fast_shutdown = 0"/etc/init.d/mysql stopcd /var/lib/mysqlmv ib_logfile0 ib_logfile0.bakmv ib_logfile1 ib_logfile1.bak/etc/init.d/mysql start
第一个命令确保 InnoDB 执行彻底关闭,这有时可以帮助解决这种情况,值得在这里包括。
C/3.3 执行引擎交换
这是另一种相当激烈的方法,尽管它很严厉,但在我个人的经验中似乎有相当可靠的成功率,尽管它肯定取决于你正在使用什么样的环境. 此方法还要求 MySQL 能够成功启动。
要将数据库中的所有表从 InnoDB 转换为 MyISAM,请运行 MySQL 以下命令,将 db_name 替换为相关数据库名称:代码:mysql -e "SELECT concat('ALTER TABLE ', TABLE_NAME,' ENGINE=MYISAM;') FROM Information_schema.TABLES WHERE TABLE_SCHEMA = 'db_name' AND ENGINE = 'InnoDB' AND TABLE_TYPE = 'BASE TABLE'"然后,在停止 MySQL 之后,您希望将 ibdata* 和 ib_logfiles 排除在外:代码:/etc/init.d/mysql stopmkdir -p /root/innodb.bak
mv ib* /root/innodb.bak/
/etc/init.d/mysql start现在您已经使用 MyISAM 启动了 MySQL 表,是时候将它们转换回 InnoDB,手指交叉(再次将 db_name 替换为您的数据库名称):代码:mysql -e "SELECT concat('ALTER TABLE ', TABLE_NAME,' ENGINE=InnoDB;') FROM Information_schema.TABLES WHERE TABLE_SCHEMA = 'db_name' AND ENGINE = 'MyISAM'"
C/4 数据字典问题
虽然这些错误各不相同,但最常见的数据字典问题与表空间或表文件不处于 InnoDB 期望它们所处的状态有关,通常是由于不正确删除 InnoDB .ibd 或 . frm 文件,或由于删除/移动 ibdata 文件。考虑到这一点,现在是注意以下事项的好时机:
C/4.1 如何正确删除 .ibd 文件
数据库各自子目录中的 .ibd 文件表示这些数据库中表的表空间。删除文件本身会导致 InnoDB 为维护数据一致性而保留的记录出现许多问题。如果你只需要出于任何原因删除表空间(例如尝试导入新的表空间/ibd 文件),正确的方法是使用 ALTER TABLE ... DISCARD TABLESPACE 语句,例如:
代码:
mysql -e "ALTER TABLE roundcube.staff DISCARD TABLESPACE"
在上面的例子中,'roundcube' 是数据库,'staff' 是表。如果您在执行此操作后检查数据库目录,您会注意到虽然该表的 .frm 文件仍然存在,但 .ibd 文件不存在。但请记住,*表*条目本身仍然存在于服务器上**。
注意: 通常,您必须在执行此操作之前禁用 foreign_key_checks,这可以通过以下方式完成:
代码:
SET FOREIGN_KEY_CHECKS=0;
C/4.2 .ibd 文件丢失后重新创建表
如果您删除或更改了表空间 (.ibd) 文件,您很可能会遇到如下所示的错误:
代码:
[ERROR] MySQL is trying to open a table handle but the .ibd file for table dbname/tblname does not exist.
在这种情况下,它仍然认为该表以某种方式存在,这意味着在解决该问题之前您将无法正确地重新创建该表。幸运的是,尽管执行失败,InnoDB 足够聪明,可以意识到发生了什么,并且如果您运行以下命令(在适当的地方替换 dbname 和 tblname),它会执行几个方便的过程:
第一步是尝试摆脱表空间的剩余部分:代码:ALTER TABLE dbname.tblname DISCARD TABLESPACE;它可能会或可能不会在 MySQL shell 上出错,但如果您查看错误日志,它仍然会继续并清理缓冲区:代码:InnoDB: Warning: cannot delete tablespace 251 in DISCARD TABLESPACE.InnoDB: But let us remove the insert buffer entries for this tablespace.然后,尝试删除实际的表记录(如果您需要保存 .frm 文件以再次获取 create table 语句,请确保在执行此步骤之前已复制它):代码:DROP TABLE dbname.tblname;您很可能会遇到类似的错误,但以下内容将再次出现在日志中:代码:InnoDB: We removed now the InnoDB internal data dictionary entry
InnoDB: of table `dbname/tblname`.之后,您可以使用备份或复制的 .frm 文件(使用上述 mysqlfrm 方法)重新创建表。
C/4.3 .frm 文件存在但表不存在
这与上一期的思路相同,只是情况更简单,更容易解决,最终:
代码:
InnoDB: Cannot find table test/child2 from the internal data dictionaryInnoDB: of InnoDB though the .frm file for the table exists.
在这里,最常见的问题是部分执行的 drop 或 alter 语句没有导致所有表的文件都被正确删除。在这些情况下,可以安全地删除 .frm 文件,InnoDB 应该相应地进行调整。但是,我的建议不是删除它,而是将其复制到备份文件夹中,至少是暂时的,以便您确实拥有 CREATE TABLE 语句,如果您出于任何原因需要访问它。
如果您不打算删除此表,或者 .ibd 文件已通过其他方式删除,那么 - 除了通过挖掘 ibdata 文件尝试进行一些深度数据恢复之外 - 您恢复此表的唯一选择是从备份中恢复,因为 InnoDB 本质上是在错误中指出这个 .frm 文件绝对是有问题的表中唯一剩下的东西。如果是这种情况,绝对不要删除 .frm 文件 - 只需重新定位它,这样您就有机会重新创建表并重新构建数据。
C/4.4 孤立表或缺少 .frm 文件
如果 .frm 文件因任何原因丢失,您可能会看到如下错误:
代码:
InnoDB: Error: table dbname/tblname already exists in InnoDB internalInnoDB: data dictionary. Have you deleted the .frm fileInnoDB: and not used DROP TABLE? ...
在这种情况下,通常遵循此错误的说明描述了处理此错误的最有效方法:
代码:
InnoDB: You can drop the orphaned table inside InnoDB byInnoDB: creating an InnoDB table with the same name in anotherInnoDB: database and moving the .frm file to the current database.InnoDB: Then MySQL thinks the table exists, and DROP TABLE willInnoDB: succeed.
这意味着,如果您有孤立表的 CREATE TABLE 语句,无论是通过备份还是其他方式,您都可以简单地创建一个测试数据库,并在测试数据库中创建表的副本(仅结构)。这将创建一个可用的 .frm 文件,该文件可用于复制到原始数据库,并替换为替换丢失的 .frm,最后允许您删除表。这是一个简短的示例,假设原始数据库和表名都只是“测试”:
代码:
# mysqlmysql> CREATE DATABASE test2;mysql> CREATE TABLE ... CHARSET=utf8;mysql> quit# cp /var/lib/mysql/test2/test.frm /var/lib/mysql/test/# mysqlmysql> SET FOREIGN_KEY_CHECKS=0;mysql> DROP TABLE test.test;
结论
InnoDB 的数据一致性标准是一把双刃剑。当它被仔细管理并充分了解它的运作方式时,它是一个了不起的引擎,但不幸的是,当它涉及到环境的变化时,它并不是最宽容的。它有一些自己处理情况的好方法,以及一些出色的错误日志记录,但在确保环境稳定时,它绝对需要你注意。
我强烈建议,如果您有兴趣阅读 InnoDB 及其功能/特性,请查看以下一些阅读材料:
MySQL :: MySQL 5.5 Reference Manual :: 14 The InnoDB Storage Engine On learning InnoDB :核心之旅——杰里米·科尔 您搜索了 Innodb - MySQL 性能博客
如果还是有问题,先不要惊慌,至少确保做了“第一反应”中描述的备份,这样如果最后真的需要数据恢复服务,会有问题发生后不就的备份文件,以免因为问题发生后,因为写入次数过多恢复不成功的情况。
电脑