15 章:存储引擎和表类型

目录

15.1. MyISAM存储引擎

15.1.1. MyISAM启动选项

15.1.2.键所需的空间

15.1.3. MyISAM表的存储格式

15.1.4. MyISAM表的问题

15.2. InnoDB存储引擎

15.2.1. InnoDB概述

15.2.2. InnoDB联系信息

15.2.3. InnoDB配置

15.2.4. InnoDB启动选项

15.2.5. 创建InnoDB 表空间

15.2.6. 创建InnoDB 表

15.2.7. 添加和删除InnoDB 数据和日志文件

15.2.8. InnoDB数据库的备份和恢复

15.2.9. InnoDB 数据库移到另一台机器

15.2.10. InnoDB事务模型和锁定

15.2.11. InnoDB性能调节提示

15.2.12. 多版本的实施

15.2.13. 表和索引结构

15.2.14.文件空间管理和磁盘I/O

15.2.15. InnoDB错误处理

15.2.16. InnoDB 表的限制

15.2.17. InnoDB故障诊断和排除

15.3. MERGE存储引擎

15.3.1. MERGE表 方面的问题

15.4. MEMORY(HEAP)存储引擎

15.5. BDB(BerkeleyDB)存储引擎

15.5.1. BDB支持的操作系统

15.5.2. 安装BDB

15.5.3. BDB启动选项

15.5.4. BDB表的特性

15.5.5. 修改BDB所需的事宜

15.5.6. BDB表的限制

15.5.7. 使用BDB表时可能 出现的错误

15.6. EXAMPLE存储引擎

15.7. FEDERATED存储引擎

15.7.1. 安装FEDERATED 存储引擎

15.7.2. FEDERATED存储引擎的介绍

15.7.3. 如何使用FEDERATED 表

15.7.4. FEDERATED存储引擎的局限

15.8. ARCHIVE存储引擎

15.9. CSV存储引擎

15.10.BLACKHOLE存储引擎

MySQL 支持数个存储引擎作为对不同表的类型的处理器。MySQL 存储引擎包括处理事务安全表的引擎和处理非事务安全表的引擎:

·         MyISAM 管理非事务表。它提供高速存储和检索,以及全文搜索能力。MyISAM 在所有 MySQL 配置里被支持,它是默认的存储引擎,除非你配置 MySQL 默认使用另外一个引擎。

·         MEMORY 存储引擎提供“内存中”表。MERGE 存储引擎允许集合将被处理同样的 MyISAM 表作为一个单独的表。就像 MyISAM 一样,MEMORY 和 MERGE 存储引擎处理非事务表,这两个引擎也都被 默认包含在 MySQL 中。

注释:MEMORY 存储引擎正式地被确定为 HEAP 引擎。

·         InnoDB 和 BDB 存储引擎提供事务安全表。BDB 被包含在为支持它的操作系统发布的 MySQL-Max 二进制分发版里。InnoDB 也 默认被包括在所有 MySQL 5.1 二进制分发版里,你可以按照喜好通过配置 MySQL 来允许或禁止任一引擎。

·         EXAMPLE 存储引擎是一个“存根”引擎,它不做什么。你可以用这个引擎创建表,但没有数据被存储于其中或从其中检索。这个引擎的目的是服务 ,在 MySQL 源代码中的一个例子,它演示说明如何开始编写新存储引擎。同样,它的主要兴趣是对开发者

·         NDB Cluster 是被 MySQL Cluster 用来实现分割到多台计算机上的表的存储引擎。它在 MySQL-Max 5.1 二进制分发版里提供。这个存储引擎当前只被 Linux, Solaris, 和 Mac OS X 支持。在未来的 MySQL 分发版中,我们想要添加其它平台对这个引擎的支持,包括 Windows。

·         ARCHIVE 存储引擎被用来无索引地,非常小覆盖存储的大量数据。

·         CSV 存储引擎把数据以逗号分隔的格式存储在文本文件中。

·         BLACKHOLE 存储引擎接受但不存储数据,并且检索总是返回一个空集。

·         FEDERATED 存储引擎把数据存在远程数据库中。在 MySQL 5.1 中,它只和 MySQL 一起工作,使用 MySQL C Client API。在未来的分发版中,我们想要让它使用其它驱动器或客户端连接方法连接到另外的数据源。

寻求选择一个存储引擎的帮助,请参阅14.4 节,“选择一个存储引擎”

这一章讲述除 NDB Cluster 外的每一个 MySQL 存储引擎,NDB Cluster 在第 17 章:MySQL Cluster中介绍。

当年创建一个新表的时候,你可以通过添加一个 ENGINE 或 TYPE 选项到 CREATE TABLE 语句来告诉 MySQL 你要创建什么类型的表:

CREATE TABLE t (i INT) ENGINE = INNODB;
CREATE TABLE t (i INT) TYPE = MEMORY;

虽然 TYPE 仍然在 MySQL 5.1 中被支持,现在 ENGINE 是首选的术语。

如果你省略掉 ENGINE 或 TYPE 选项,默认的存储引擎被使用。一般的默认是 MyISAM,但你可以用--default-storage-engine 或--default-table-type 服务器启动选项来改变它,或者通过设置 storage_engine 或 table_type 系统变量来改变。

当 MySQL 被用 MySQL 配置向导安装在 Windows 平台上,InnoDB 存储引擎替代 MyISAM 存储引擎作为替代,请参阅2.3.5.1 节,“介绍”

要把一个表从一个类型转到另一个类型,可使用 ALTER TABLE 语句,这个语句指明新的类型:

ALTER TABLE t ENGINE = MYISAM;
ALTER TABLE t TYPE = BDB;

请参阅13.1.5 节,“CREATE TABLE 语法”13.1.2 节,“ALTER TABLE 语法”

如果你试着使用一个未被编译进 MySQL 的存储引擎,或者试着用一个被编译进 MySQL 但没有被激活的存储引擎,MySQL 取而代之地创建一个 MyISAM 类型的表。当你在支持不同存储引擎的 MySQL 服务器之间拷贝表的时候,上述的行为是很方便的。(例如,在一个复制建立中,可能你的主服务器为增加安全而支持 事务存储引擎,但从服务器为更快的速度而仅使用非事务存储引擎。)

在不可用的类型被指定时,自动用 MyISAM 表来替代,这会对 MySQL 的新用户造成混淆。无论何时一个表被自动改变之时,产生一个警告。

MySQL 总是创建一个。frm 文件来保持表和列的定义。表的索引和数据可能被存储在一个或多个文件里,这取决于表的类型。服务器在存储引擎级别之上创建。frm 文件。单独的存储引擎创建任何需要用来管理表的额外文件。

一个数据库可以包含不同类型的表。

事务安全表 (TST) 比起非事务安全表 (NTST) 有几大优势:

·         更安全。即使 MySQL 崩溃或遇到硬件问题,要么自动恢复,要么从备份加事务日志恢复,你可以取回数据。

·         你可以合并许多语句,并用 COMMIT 语句同时接受它们全部 (如果 autocommit 被禁止掉)。

·         你可以执行 ROLLBACK 来忽略你的改变 (如果 autocommit 被禁止掉)。

·         如果更新失败,你的所有改变都变回原来。(用非事务安全表,所有发生的改变都是永久的)。

·         事务安全存储引擎可以给那些当前用读得到许多更新的表提供更好的部署。

虽然 MySQL 支持数个事务安全存储引擎,为获得最好结果,你不应该在一个事务那混合不同表类型。如果你混合表类型会发生问题,更多信息请参阅13.4.1 节,“START TRANSACTION, COMMIT 和 ROLLBACK Syntax”

如果你没有指定配置值的话,InnoDB 使用默认的配置值。请参阅15.2.3 节,“InnoDB 配置”

非事务安全表自身有几个优点,因为没有事务开支,所有优点都能出现:

·         更快

·         需要更少的磁盘空间

·         执行更新需要更少的内存

你可以在同一个语句中合并事务安全和非事务安全表来获得两者最好的情况。尽管如此,在 autocommit 被禁止掉的事务里,变换到非事务安全表依旧即时提交,并且不会被回滚。

15.1. MyISAM存储引擎

15.1.1. MyISAM启动选项

15.1.2.键需要的空间

15.1.3. MyISAM表存储格式

15.1.4. MyISAM表的问题

MyISAM 是 默认存储引擎。它基于更老的 ISAM 代码,但有很多有用的扩展。(注意 MySQL 5.1 不支持 ISAM)。

每个 MyISAM 在磁盘上存储成三个文件。第一个文件的名字以表的名字开始,扩展名指出文件类型。.frm 文件存储表定义。数据文件的扩展名为。MYD (MYData)。索引文件的扩展名是。MYI (MYIndex)。

要明确表示你想要用一个 MyISAM 表格,请用 ENGINE 表选项指出来:

CREATE TABLE t (i INT) ENGINE = MYISAM;

注释:老版本的 MySQL 使用 TYPE 而不是 ENGINE(例如,TYPE = MYISAM)。MySQL 5.1 为向下兼容而支持这个语法,但 TYPE 现在被轻视,而 ENGINE 是首先的用法。

一般地,ENGINE 选项是不必要的;除非默认已经被改变了,MyISAM 是默认存储引擎。

你可以用myisamchk工具来检查或修复 MyISAM 表。请参阅5.9.5.6 节,“使用myisamchk做崩溃恢复”。你也可以用myisampack来压缩 MyISAM 表,让它们占更少的空间。请参阅8.2 节,“myisampack,产生压缩、只读的 MyISAM 表”

如下是 MyISAM 存储引擎的一些特征:

·         所有数据值先存储低字节。这使得数据机和操作系统分离。二进制轻便性的唯一要求是机器使用补码 (如最近 20 年的机器有的一样) 和 IEEE 浮点格式 (在主流机器中也完全是主导的)。唯一不支持二进制兼容性的机器是嵌入式系统。这些系统有时使用特殊的处理器。

先存储数据低字节并不严重地影响速度;数据行中的字节一般是未联合的,从一个方向读未联合的字节并不比从反向读更占用更多的资源。服务器上的获取列值的代码与其它代码相比并不显得时间紧。

·        大文件 (达 63 位文件长度) 在支持大文件的文件系统和操作系统上被支持。

·         当把删除和更新及插入混合的时候,动态尺寸的行更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块来自动完成。

·         每个 MyISAM 表最大索引数是 64。 这可以通过重新编译来改变。每个索引最大的列数是 16 个。

·         最大的键长度是 1000 字节。这也可以通过编译来改变。对于键长度超过 250 字节的情况,一个超过 1024 字节的的键块被用上。

·         BLOB 和 TEXT 列可以被索引。

·         NULL 值被允许在索引的列中。这个占每个键的 0-1 个字节。

·         所有数字键值以高字节为先被存储以允许一个更高地索引压缩。

·        当记录以排好序的顺序插入 (就像你使用一个 AUTO_INCREMENT 列之时),索引树被劈开以便高节点仅包含一个键。这改善了索引树的空间利用率。

·         每表一个 AUTO_INCREMEN 列的内部处理。MyISAM 为 INSERT 和 UPDATE 操作自动更新这一列。这使得 AUTO_INCREMENT 列更快 (至少 10%)。在序列顶的值被删除之后就不能再利用。(当 AUTO_INCREMENT 列被定义为多列索引的最后一列,可以出现重使用从序列顶部删除的值的情况 )。AUTO_INCREMENT 值可用 ALTER TABLE 或myisamch来重置。

·         如果数据文件中间的表没有自由块了,在其它线程从表读的同时,你可以 INSERT 新行到表中。(这被认识为并发操作 )。自由块的出现是作为删除行的结果,或者是用比当前内容多的数据对动态长度行更新的结果。当所有自由块被用完 (填满),未来的插入又变成并发。

·         你可以把数据文件和索引文件放在不同目录,用 DATA DIRECTORY 和 INDEX DIRECTORY 选项 CREATE TABLE 以获得更高的速度,请参阅13.1.5 节,“CREATE TABLE 语法”

·         每个字符列可以又不同的字符集,请参阅第 10 章 :“字符集支持”

·         在 MyISAM 索引文件里又一个标志,它表明表是否被正确关闭。如果用--myisam-recover 选项启动mysqld,MyISAM 表在打开得时候被自动检查,如果被表被不恰当地关闭,就修复表。

·         如果你用--update-state 选项运行myisamchk,它标注表为已检查。myisamchk --fast只检查那些没有这个标志的表。

·         myisamchk --analyze为部分键存储统计信息,也为整个键存储统计信息。

·         myisampack可以打包 BLOB 和 VARCHAR 列。

MyISAM 也支持下列特征:

·         支持 true VARCHAR 类型;VARCHAR 列以存储在 2 个字节中的长度来开始。

·         有 VARCHAR 的表可以有固定或动态记录长度。

·         VARCHAR 和 CHAR 列可以多达 64KB。

·         一个被搞乱的已计算索引对可对 UNIQUE 来使用。这允许你在表内任何列的合并上有 UNIQUE。(尽管如此,你不能在一个 UNIQUE 已计算索引上搜索)。

对 MyISAM 存储引擎,有一个更详细的论坛在http://forums.mysql.com/list.php?21

15.1.1MyISAM启动选项

下列对mysqld 的选项可用来改变 MyISAM 表的行为:

·         --myisam-recover=mode

设置为崩溃 MyISAM 表自动恢复的模式。

·         --delay-key-write=ALL

对任何 MyISAM 表的写操作之间不要刷新键缓冲区。

注释:如果你要这么做。当表在使用中之时,你应该不使用来自另一个程序的 MyISAM 表 (比如从另一个 MySQL 服务器或用myisamchk)。这么做会导致索引被破坏。

对使用--delay-key-write 的表,使用--external-locking 没有帮助。

请参阅5.3.1 节,“mysqld命令行选项”

下列系统变量影响 MyISAM 表的行为:

·         bulk_insert_buffer_size

用在块插入优化中的树缓冲区的大小。注释:这是一个per thread的限制。

·         (OBSOLETE) myisam_max_extra_sort_file_size

这个参数已经不在 MySQL 中使用。

·         myisam_max_sort_file_size

如果临时文件会变得超过索引,不要使用快速排序索引方法来创建一个索引。注释:这个参数以字节的形式给出。

·         myisam_sort_buffer_size

设置恢复表之时使用的缓冲区的尺寸。

请参阅5.3.3 节,“服务器系统变量”

如果用--myisam-recover 选项启动mysqld,自动恢复被激活。在这种情况下,当服务器打开一个 MyISAM 表之时,服务器会检查是否表被标注为崩溃,或者表的打开计数变量是否不为 0 且你正用--skip-external-locking 运行服务器。如果这些条件的任何一个为真,下列情况发生:

·         表被查错。

·         如果服务器发现一个错误,它试着做快速表修复 (排序且不重新创建数据文件)。

·         如果修复因为数据文件中的一个错误而失败 (例如,一个重复键错误),服务器会再次尝试修复,这一次重建数据文件。

·         如果修复仍然失败,服务器用旧修复选项方法再重试一次修复 (一行接一行地写,不排序)。这个方法应该能修复任何类型的错误,并且需要很低的磁盘空间。

如果恢复不能够从先前完成的语句里恢复所有行,而且你不能在--myisam-recover 选项值指定 FORCE,自动修复会终止,并在错误日志里写一条错误信息:

Error: Couldn't repair table: test.g00pages

如果你指定 FORCE,取而代之地,类似这样的一个警告被给出:

Warning: Found 344 of 354 rows when repairing ./test/g00pages

注释:如果自动恢复值包括 BACKUP,恢复进程创建文件并用tbl_name-datetime.BAK 形式取名。你应该有一个cron脚本,它自动把这些文件从数据库目录移到备份媒质上。

15.1.2.键所需的空间

MyISAM 表使用 B 型树索引。你可以粗略地计算索引文件的大小为 (key_length+4)/0.67, 加上所有的键之和。当所有键以排序的顺序插入并且表没有任何压缩的键之时,以上估计是对最坏的情况的。

字符串索引是被空间压缩的。如果第一个字符串索引部分是字符串,它也被加前缀压缩。如果字符串列有许多拖曳空间,或字符串 列是一个总是不用完全长度的 VARCHAR 列,空间压缩使得索引文件比最坏情况时的数值要小。前缀压缩被用在以字符串开始的键上。如果有许多具有同一前缀的字符串,前缀压缩是有帮助的。

在 MyISAM 表,你也可以在创建表的时候通过指定 PACK_KEYS=1 来前缀压缩数字。当数字被以高字节优先存储之时,若你有许多具有同一前缀的整数 键,上述方法是有帮助的。

15.1.3MyISAM表的存储格式

15.1.3.1. 静态 (固定长度) 表特征

15.1.3.2. 动态表特征

15.1.3.3. 已压缩表特征

MyISAM 支持三种不同存储格式。其中两个 (固定格式和动态格式) 根据正使用的列的类型来自动选择。第三个,即已压缩格式,只能使用myisampack工具来创建。

当你 CREATE 或 ALTER 一个没有 BLOB 或 TEXT 列的表,你可以用 ROW_FORMAT 表选项强制表的格式为 FIXED 或 DYNAMIC。这 会导致 CHAR 和 VARCHAR 列因 FIXED 格式变成 CHAR,或因 DYNAMIC 格式变成 VARCHAR。

通过用 ALTER TABLE 指定 ROW_FORMAT={COMPRESSED | DEFAULT},你可以压缩或解压缩表,请参阅13.1.5 节,“CREATE TABLE 语法”

15.1.3.1. ;静态 (固定长度) 表特征

静态格式是 MyISAM 表的默认存储格式。当表不包含变量长度列 (VARCHAR, BLOB,或 TEXT) 时,使用这个格式。每一行用固定字节数存储。

MyISAM 的三种存储格式中,静态格式就最简单也是最安全的 (至少对于崩溃而言)。静态格式也是最快的 on-disk 格式。快速来自于数据文件中的行在磁盘上被找到的容易方式:当按照索引中的行号查找一个行时,用行长度乘以行号。同样,当扫描一个表的 时候,很容易用每个磁盘读操作读一定数量的记录。

当 MySQL 服务器正往一个固定格式 MyISAM 文件写的时候,如果计算机崩溃了,安全是显然的。在这种情况下,myisamchk可以容易地决定每行从哪里开始到哪里结束,所以它通常可以收回所有记录,除了写了一部分的记录。注意,基于数据行,MyISAM 表索引可以一直被重新构建。

静态格式表的一般特征:

·         CHAR 列对列宽度是空间填补的。

·         非常快。

·         容易缓存。

·         崩溃后容易重建,因为记录位于固定位置。

·         重新组织是不必要的,除非你删除巨量的记录并且希望为操作系统腾出磁盘空间。为此,可使用 OPTIMIZE TABLE 或者myisamchk -r

·         通常比动态格式表需要更多的磁盘空间。

15.1.3.2. ;动态表特征

如果一个 MyISAM 表包含任何可变长度 列 (VARCHAR, BLOB 或 TEXTDynamic),或者如果一个表被用 ROW_FORMAT=DYNAMIC 选项来创建,动态存储格式被使用。

这个格式更为复杂一点,因为每行有一个表明行有多长的头。当一个记录因为更新的结果被变得更长,该记录也可以在超过一个位置处结束。

你可以使用 OPTIMIZE TABLE 或myisamchk来对一个表整理碎片。如果在一个表中有你频繁访问或改变的固定长度 列,表中也有一些可变长度列,仅为避免碎片而把这些可变长度列移到其它表可能是一个好主意。

动态格式表的一般特征:

·         除了长度少于 4 的列外,所有的字符串列是动态的。

·         在每个记录前面是一个位图,该位图表明哪一列包含空字符串 (对于字符串列) 或者 0(对于数字列)。注意,这并不包括包含 NULL 值的列。如果一个字符列在拖曳空间移除后长度为零,或者一个数字 列为零值,这都在位图中标注了且列不被保存到磁盘。 非空字符串被存为一个长度字节加字符串的内容。

·         通常比固定长度表需要更少的磁盘空间。

·         每个记录仅使用必需大小的空间。尽管如此,如果一个记录变大,它就按需要被分开成多片,造成记录碎片的后果。比如,你用扩展行长度的信息更新一行,该行就变得有碎片。在这种情况下,你可以时不时运行 OPTIMIZE TABLE 或myisamchk -r来改善性能。可使用myisamchk -ei来获取表的统计数据。

·         动态格式表在崩溃后要比静态格式表更难重建,因为一个记录可能被分为多个碎片且链接 (碎片) 可能被丢失。

·         动态尺寸记录期望的行长度用下列表达式来计算:

·                3
·                + (number of columns + 7) / 8
·                + (number of char columns)
·                + (packed size of numeric columns)
·                + (length of strings)
·                + (number of NULL columns + 7) / 8

对每个链接需要额外的 6 字节。在一个更新导致一个记录的扩大之时,一个动态记录被链接了。每个新链接至少是 20 字节,所以下一个扩大可能在同样的链接里进行。如果不是,则另一个链接将被建立。你可以使用myisamchk -ed来找出链接的数目。所有的链接可以用myisamchk -r来移除。

15.1.3.3. ;已压缩表特征

已压缩存储格式是由myisampack工具创建的只读格式。

所有 MySQL 分发版里都默认包括myisampack。已压缩表可以用myisamchk来解压缩。

已压缩表有下列特征:

·         已压缩表占据非常小的磁盘空间。这最小化了磁盘用量,当使用缓慢的磁盘 (如 CD-ROM) 之时,这是很有用的。

·         每个记录是被单独压缩的,所以只有非常小的访问开支。依据表中最大的记录,一个记录的头在每个表中占据 1 到 3 个字节。每个 列被不同地压缩。通常每个列有一个不同的 Huffman 树。一些压缩类型如下:

o        后缀空间压缩。

-        前缀空间压缩。

-        零值的数用一个位来存储。

-        如果在一个整型列中的值有一个小的范围,列被用最小可能的类型来存储。比如,一个 BIGINT 列 (8 字节),如果所有它的值在-128 到 127 范围内,它可以被存储为 TINYINT 列 (1 字节)

-        如果一个 列仅有一小组可能的值,列的类型被转化成 ENUM。

-        一个 列可以使用先前压缩类型的任意合并。

·         可以处理固定长度或动态长度记录。

15.1.4MyISAM表 方面的问题

15.1.4.1. 损坏的MyISAM 表

15.1.4.2. 未被适当关闭的表的问题

MySQL 用来存储数据的文件格式已经被广泛测试过,但总是有导致数据表变得损坏的环境。

15.1.4.1. ;损坏的 MyISAM 表

即使 MyISAM 表格式非常可靠 (SQL 语句对表做的所有改变在语句返回之前被写下),如果下列任何事件发生,你依然可以获得损坏的表:

·         mysqld进程在写中间被杀掉。

·         发生未预期的计算机关闭 (例如,计算机被关闭)。

·         硬件故障。

·         你可以同时在正被服务器修改的表上使用外部程序 (如myisamchk)。

·         MySQL 或 MyISAM 代码的软件缺陷。

一个损坏的表的典型症状如下:

·         当在从表中选择数据之时,你得到如下错误:

·                Incorrect key file for table: '...'. Try to repair it

·         查询不能在表中找到行或返回不完全的数据。

你可以用 CHECK TABLE statement语句来检查MyISAM的健康,并用 REPAIR TABLE 修复一个 损坏的 MyISAM 表。当mysqld不运行之时,你也可以用myisamchk命令检查或修理一个表。请参阅13.5.2.3 节,“CHECK TABLE 语法” 13.5.2.6 节,“REPAIR TABLE 语法”,和5.9.5 节,“myisamchk — MyISAM 表维护工具”

如果你的表变得频繁损坏,你应该试着确定为什么会这样的原因。要明白的最重要的事是表变得损坏是不是因为服务器崩溃的结果。你可以在错误日志中查找最近的 restarted mysqld 消息来早期验证这个。如果存在这样一个消息,则表损坏是服务器死掉的一个结果是很有可能的。否则,损坏可能在正常操作中发生。这是一个缺陷。你应该试着创建一个展示这个问题的可重复生成的测试案例。请参阅A.4.2 节,“如果 MySQL 保持崩溃,该怎么做”E.1.6 节,“如果出现表崩溃,请生成测试案例”

15.1.4.2. ;未被适当关闭的表的问题

每个 MyISAM 索引文件 (.MYI) 在头有一个计数器,它可以被用来检查一个表是否被恰当地关闭。如果你从 CHECK TABLE 或myisamchk得到下列警告,意味着这个计数器已经不同步了:

clients are using or haven't closed the table properly

这个警告并不是完全意味着表已被破坏,但你至少应该检查表。

计数器的工作方式如下:

·         表在 MySQL 中第一次被更新,索引文件头的计数器加一。

·         在未来的更新中,计数器不被改变。

·         当表的最后实例被关闭 (因为一个操作 FLUSH TABLE 或因为在表缓冲区中没有空间) 之时,若表已经在任何点被更新,则计数器减一。

·         当你修理或检查表并且发现表完好之时,计数器被重置为零。

·         要避免与其它可能检查表的进程进行事务的问题,若计数器为零,在关闭时计数器不减一。

换句话来说,计数器只有在下列情况会不同步:

·         MyISAM 表不随第一次发出的 LOCK TABLES 和 FLUSH TABLES 被复制。

·         MySQL 在一次更新和最后关闭之间崩溃 (注意,表可能依然完好,因为 MySQL 总是在每个语句之间为每件事发出写操作)。

·         一个表被myisamchk --recovermyisamchk --update-state修改,同时被mysqld使用。

·         多个mysqld服务器正使用表,并且一个服务器在一个表上执行 REPAIR TABLE 或 CHECK TABLE,同时该表也被另一个服务器使用。在这个结构中,使用 CHECK TABLE 是安全的,虽然你可能从其它服务器上得到警告。尽管如此,REPAIR TABLE 应该被避免,因为当一个服务器用一个新的数据文件替代旧的之时,这并没有发送信号到其它服务器上。

总的来说,在多服务器之间分享一个数据目录是一个坏主意。请参阅5.12 节,“在同一个机器上运行多个 MySQL 服务器” 获得更多地讨论

15.2. InnoDB存储引擎

15.2.1. InnoDB概述

15.2.2. InnoDB联系信息

15.2.3. InnoDB配置

15.2.4. InnoDB启动选项

15.2.5. 创建InnoDB 表空间

15.2.6. 创建InnoDB 表

15.2.7. 添加和删除InnoDB 数据和日志文件

15.2.8. InnoDB数据库的备份和恢复atabase

15.2.9. InnoDB 数据库移到另一台机器上

15.2.10. InnoDB事务模型和锁定

15.2.11. InnoDB性能调节提示

15.2.12. 多版本的实施

15.2.13. 表和索引结构

15.2.14.文件空间管理和磁盘I/O

15.2.15. InnoDB错误处理

15.2.16. InnoDB 表的限制

15.2.17. InnoDB故障诊断和排除

15.2.1InnoDB概述

InnoDB 给 MySQL 提供 了具有提交,回滚和崩溃恢复能力的事务安全 (ACID 兼容) 存储引擎。InnoDB 锁定在行级并且也在 SELECT 语句提供一个 Oracle 风格一致的非锁定读。这些特色增加 了多用户部署和性能。没有在 InnoDB 中扩大锁定的需要,因为在 InnoDB 中行级锁定适合非常小的空间。InnoDB 也支持 FOREIGN KEY 强制。在 SQL 查询中,你可以自由地将 InnoDB 类型的表与其它 MySQL 的表的类型混合起来,甚至在同一个查询中也可以混合。

InnoDB 是为处理巨大数据量时的最大性能设计。它的 CPU 效率可能是任何其它基于磁盘的关系数据库引擎所不能匹敌的。

InnoDB 存储引擎被完全与 MySQL 服务器整合,InnoDB 存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB 存储它的表&索引在一个表空间中,表空间可以包含数个文件 (或原始磁盘分区)。这与 MyISAM 表不同,比如在 MyISAM 表中每个表被存在分离的文件中。InnoDB 表可以是任何尺寸,即使在文件尺寸被限制为 2GB 的操作系统上。

InnoDB 默认地被包含在 MySQL 二进制分发中。Windows Essentials installer 使 InnoDB 成为 Windows 上 MySQL 的 默认表。

InnoDB 被用来在众多需要高性能的大型数据库站点上产生。著名的 Internet 新闻站点 Slashdot.org 运行在 InnoDB 上。Mytrix, Inc.在 InnoDB 上存储超过 1TB 的数据,还有一些其它站点在 InnoDB 上处理平均每秒 800 次插入/更新的负荷。

InnoDB 在和 MySQL 一样在同一个 GNU GPL 证书,第 2 版 (1991 年 6 月版) 下发行。更多有关 MySQL 证书的信息,请参阅 http://www.mysql.com/company/legal/licensing/。

关于 InnoDB 存储引擎,在http://forums.mysql.com/list.php?22有一个详细的论坛。

15.2.2InnoDB联系信息

InnoDB 引擎的厂家的联系信息,Innobase Oy 的联系方式如下:

Web site: http://www.innodb.com/
Email: <sales@innodb.com>
Phone: +358-9-6969 3250 (office)
       +358-40-5617367 (mobile)
 
Innobase Oy Inc.
World Trade Center Helsinki
Aleksanterinkatu 17
P.O.Box 800
00101 Helsinki
Finland

15.2.3InnoDB配置

InnoDB 存储引擎是默认地被允许的。如果你不想用 InnoDB 表,你可以添加 skip-innodb 选项到 MySQL 选项文件。

被 InnoDB 存储引擎管理的两个重要的基于磁盘的资源是 InnoDB 表空间数据文件和它的日志文件。

如果你指定无 InnoDB 配置选项,MySQL 将在 MySQL 数据目录下创建一个名为 ibdata1 的 10MB 大小的自动扩展数据文件,以及两个名为 ib_logfile0 和 ib_logfile1 的 5MB 大小的日志文件。

注释:InnoDB 给 MySQL 提供具有提交, 回滚和崩溃恢复能力的事务安全 (ACID 兼容) 存储引擎。如果拟运行的操作系统和硬件不能如广告说的那样运行,InnoDB就不能实现如上能力。许多操作系统或磁盘子系统可能为改善性能而延迟或记录写操作。在一些操作系统上,就是系统调用 (fsync()) 也要等着,直到所有未写入已被刷新文件的数据在被刷新到稳定内存之前可以确实返回了。因为这个,操作系统崩溃或掉电可能损坏当前提交的数据,或者在最坏的 情况,因为写操作已被记录了,甚至破坏了数据库。如果数据完整性对你很重要,你应该在用任何程序于生产中之前做一些“pull-the-plug&rdquo;测试。Mac OS X 10.3 及以后版本,InnoDB 使用一个特别的 fcntl() 文件 刷新方法。在 Linux 下,建议禁止回写缓存。

在 ATAPI 硬盘上,一个类似 hdparm -W0 /dev/hda命令可能起作用。小心某些驱动器或者磁盘控制器可能不能禁止回写缓存。

注释:要获得好的性能,你应该如下面例子所讨论那样,明确提供 InnoDB 参数。自然地,你应该编辑设置来适合你的硬件和要求。

要建立 InnoDB 表空间文件,在 my.cnf 选项文件里的 [mysqld] 节里使用 innodb_data_file_path 选项。在 Windows 上,你可以替代地使用 my.ini 文件。innodb_data_file_path 的值应该为一个或多个 数据文件规格的列表。如果你命名一个以上的数据文件,用 分号 (‘;’) 分隔它们:

innodb_data_file_path=datafile_spec1[;datafile_spec2]...

例如:把明确创建的具有相同特征的表空间作为默认设置的设置操作如下:

[mysqld]
innodb_data_file_path=ibdata1:10M:autoextend

这个设置配置一个可扩展大小的尺寸为 10MB 的单独文件,名为 ibdata1。没有给出文件的位置,所以默认的是在 MySQL 的数据目录内。

尺寸大小用 M 或者 G 后缀来指定说明单位是 MB 或者 GB。

一个表空间,它在数据目录里包含一个名为 ibdata1 的固定尺寸 50MB 的数据文件和一个名为 ibdata2 大小为 50MB 的自动扩展文件,其可以 像这样被配置:

[mysqld]
innodb_data_file_path=ibdata1:50M;ibdata2:50M:autoextend

一个指定数据文件的完全后缀包括文件名,它的尺寸和数个可选属性:

file_name:file_size[:autoextend[:max:max_file_size]]

autoextend 属性和后面跟着的属性只可被用来对 innodb_data_file_path 行里最后一个数据文件。

如果你对最后的数据文件指定 autoextend 选项。如果数据文件耗尽了表空间中的自由空间,InnoDB 就扩展数据文件。扩展的幅度是每次 8MB。

如果磁盘已满,你可能想要把其它数据添加到另一个硬盘上。重新配置一个已存在表空间的指令见15.2.7 节,“添加和删除 InnoDB 数据和日志文件”

InnoDB 并不感知最大文件尺寸,所以要小心文件系统,在那上面最大的文件尺寸是 2GB。要为一个自动扩展数据文件指定最大尺寸,请使用 max 属性。下列配置允许 ibdata1 涨到极限的 500MB:

[mysqld]
innodb_data_file_path=ibdata1:10M:autoextend:max:500M

InnoDB默认地在 MySQL 数据目录创建表空间文件。要明确指定一个位置,请使用 innodb_data_home_dir 选项。比如,要使用两个名为 ibdata1 和 ibdata2 的文件,但是要把他们创建到/ibdata, 像如下一样配置 InnoDB:

[mysqld]
innodb_data_home_dir = /ibdata
innodb_data_file_path=ibdata1:50M;ibdata2:50M:autoextend

注释:InnoDB 不创建目录,所以在启动服务器之前请确认/ibdata 目录的确存在。这对你配置的任何日志文件目录来说也是真实的。使用 Unix 或 DOS 的 mkdir 命令来创建任何必需的目录。

通过把 innodb_data_home_dir 的值原原本本地部署到数据文件名,并在需要的地方添加斜杠或反斜杠,InnoDB 为每个数据文件形成目录路径。如果 innodb_data_home_dir 选项根本没有在 my.cnf 中提到, 默认值是“dot&rdquo;目录 ./,这意思是 MySQL 数据目录。

如果你指定 innodb_data_home_dir 为一个空字符串,你可以为列在 innodb_data_file_path 值里的数据文件指定绝对路径。下面的例子等价于前面那个例子:

[mysqld]
innodb_data_home_dir =
innodb_data_file_path=/ibdata/ibdata1:50M;/ibdata/ibdata2:50M:autoextend

一个简单的my.cnf例子。假设你有一台配备 128MB 内存和一个硬盘的计算机。下面的例子显示在 my.cnf 或 my.ini 里对 InnoDB 可能的配置参数,包括 autoextend 属性。

这个例子适合大多数在 Unix 和 Windows 上,不想分配 InnoDB 数据文件和日志文件到数个磁盘上的用户。它在 MySQL 数据目录创建一个自动扩展数据文件 ibdata1 和两个日志文件 ib_logfile0 及 ib_logfile1。同样,InnoD 在数据目录里自动创建的小型档案 InnoDB 日志文件 ib_arch_log_0000000000 也结束。

[mysqld]
# You can write your other MySQL server options here
# ...
# Data files must be able to hold your data and indexes.
# Make sure that you have enough free disk space.
innodb_data_file_path = ibdata1:10M:autoextend
#
# Set buffer pool size to 50-80% of your computer's memory
set-variable = innodb_buffer_pool_size=70M
set-variable = innodb_additional_mem_pool_size=10M
#
# Set the log file size to about 25% of the buffer pool size
set-variable = innodb_log_file_size=20M
set-variable = innodb_log_buffer_size=8M
#
innodb_flush_log_at_trx_commit=1

请确认 MySQL 服务器有适当的权限在数据目录里创建文件。更一般地,服务器必须对任何它需要创建数据文件或日志文件的目录有访问权限。

注意,在一些文件系统上,数据文件必需小于 2GB。数据文件的合并尺寸必须至少 10MB。

当你第一次创建一个 InnoDB 表空间,最好从命令行来启动 MySQL 服务器。InnoDB 然后把数据库创建的信息打印到屏幕,所以你可以看见正在发生什么。比如,在 Windows 上,如果mysqld-max位于 C:\mysql\bin,你可以如下来启动它:

C:\> C:\mysql\bin\mysqld-max --console

如果你不发送服务器输出到屏幕上,请检查服务器的错误日志来看在启动过程中 InnoDB 打印了什么。

请参阅15.2.5 节,“创建 InnoDB 表空间”,以获得 InnoDB 显示的信息看起来应该 像什么的例子。

Windows 上如何指定选项? 在 Windows 上选项文件的规则如下:

·         只应该创建一个 my.cnf 或文件。

·         my.cnf 文件应该被放在 C 盘根目录。

·         my.ini 文件应该被放置在 WINDIR 目录;例如 C:\WINDOWS 或 C:\WINNT。你可以在 Windows 控制台的命令提示符使用 SET 命令来打印 WINDIR 的值:

·                C:\> SET WINDIR
·                windir=C:\WINNT

·         如果你的 PC 在 C 盘不是启动盘的地方使用启动装载机,你唯一的选择是使用 my.ini 文件。

·         如果你使用安装和配置向导安装的 MySQL,my.ini 文件被放在 MySQL 的安装目录。请参阅2.3.5.14 节,“my.ini 文件的位置”

Unix 上在哪里指定选项? 在 Unix 上,mysqld从下列文件,如果它们存在的话。以下列的顺序读取选项:

·         /etc/my.cnf

全局选项。

·         $MYSQL_HOME/my.cnf

服务器专用选项。

·         defaults-extra-file

--defaults-extra-file 选项指定的文件。

·         ~/.my.cnf

用户专用选项。

MYSQL_HOME 代表环境变量,它内含着到包含服务器专用 my.cnf 文件的目录的路径。

如果你确信mysqld只从指定文件读取选项,你可以在启动服务器之时在命令行使用--defaults-option 作为第一个选项:

mysqld --defaults-file=your_path_to_my_cnf

一个高级的my.cnf例子。假设你有一台 Linux 计算机,有 2GB 内存和三个 60GB 硬盘 (在目录路径/, /dr2 和/dr3)。下列例子显示 了在 my.cnf 里对 InnoDB 可能的配置参数。

[mysqld]
# You can write your other MySQL server options here
# ...
innodb_data_home_dir =
#
# Data files must be able to hold your data and indexes
innodb_data_file_path = /ibdata/ibdata1:2000M;/dr2/ibdata/ibdata2:2000M:autoextend
#
# Set buffer pool size to 50-80% of your computer's memory,
# but make sure on Linux x86 total memory usage is < 2GB
innodb_buffer_pool_size=1G
innodb_additional_mem_pool_size=20M
innodb_log_group_home_dir = /dr3/iblogs
#
innodb_log_files_in_group = 2
#
# Set the log file size to about 25% of the buffer pool size
innodb_log_file_size=250M
innodb_log_buffer_size=8M
#
innodb_flush_log_at_trx_commit=1
innodb_lock_wait_timeout=50
#
# Uncomment the next lines if you want to use them
#innodb_thread_concurrency=5

注意,该例子把两个数据文件放在不同磁盘上。InnoDB 开始用第一个数据文件填充表空间。在一些情况下,如果所有数据不被放置在同一物理磁盘上,这样将改善数据库的性能。把日志文件放在与数据文件不同的磁盘上对性能是经常很有好处的。你也可以使用原始磁盘分区 (原始设备) 作为 InnoDB 数据文件,这样可以加速 I/O。请参阅15.2.14.2 节,“为表空间使用原始设备”

警告:在 32 位 GNU/Linux x86 上,你必须要小心不要设置过高的内存用量。glibc 可能允许进程堆积在线程堆栈上发展,它会造成你的服务器崩溃。如果下列表达式的值接近或者超过 2GB,系统会面临危机:

innodb_buffer_pool_size
+ key_buffer_size
+ max_connections*(sort_buffer_size+read_buffer_size+binlog_cache_size)
+ max_connections*2MB

每个线程使用一个堆栈 (通常是 2MB,但在 MySQL AB 二进制分发版里只有 256KB) 并且在最坏的情况下也使用 sort_buffer_size + read_buffer_size 附加内存。

你可以自己编译 MySQL,在 32 位 Windows 上使用高达 64GB 物理内存。请参阅15.2.4 节,“InnoDB 启动选项”里对 innodb_buffer_pool_awe_mem_mb 的描述。

如何调整其它 mysqld 服务器参数?下列值是典型的,且适用于多数用户:

[mysqld]
skip-external-locking
max_connections=200
read_buffer_size=1M
sort_buffer_size=1M
#
# Set key_buffer to 5 - 50% of your RAM depending on how much
# you use MyISAM tables, but keep key_buffer_size + InnoDB
# buffer pool size < 80% of your RAM
key_buffer_size=value

15.2.4InnoDB启动选项

这一节叙述 InnoDB 相关的服务器选项,所有这些选项可以以--opt_name=value的形式在命令行或在选项文件里被指定。

·         innodb_additional_mem_pool_size

InnoDB 用来存储数据目录信息&其它内部数据结构的内存池的大小。你应用程序里的表越多,你需要在这里分配越多的内存。如果 InnoDB 用光了这个池内的内存,InnoDB 开始从操作系统分配内存,并且往 MySQL 错误日志写警告信息。 默认值是 1MB。

·         innodb_autoextend_increment

当自动扩展表空间被填满之时,为扩展而增加的尺寸 (MB 为单位)。 默认值是 8。这个选项可以在运行时作为全局系统变量而改变。

·         innodb_buffer_pool_awe_mem_mb

如果缓冲池被放在 32 位 Windows 的 AWE 内存里,这个参数就是缓冲池的大小 (MB 为单位)。(仅在 32 位 Windows 上相关) 如果你的 32 位 Windows 操作系统使用所谓的“地址窗口扩展 (AWE)&rdquo;支持超过 4GB 内存,你可以用这个参数把 InnoDB 缓冲池分配进 AWE 物理内存。这个参数最大的可能值是 64000。如果这个参数被指定了,innodb_buffer_pool_size 是在 32 位地址空间的mysqld内的窗口,InnoDB 把那个 AWE 内存映射上去。对 innodb_buffer_pool_size 参数,一个比较好的值是 500MB。

·         innodb_buffer_pool_size

InnoDB 用来缓存它的数据和索引的内存缓冲区的大小。你把这个值设得越高,访问表中数据需要得磁盘 I/O 越少。在一个专用的数据库服务器上,你可以设置这个参数达机器物理内存大小的 80%。尽管如此,还是不要把它设置得太大,因为对物理内存的竞争可能在操作系统上导致内存调度。

·         innodb_checksums

InnoDB 在所有对磁盘的页面读取上使用校验和验证以确保额外容错防止硬件损坏或数据文件。尽管如此,在一些少见的情况下 (比如运行标准检查之时) 这个额外的安全特征是不必要的。在这些情况下,这个选项 ( 默认是允许的) 可以用--skip-innodb-checksums 来关闭。

·         innodb_data_file_path

到单独数据文件和它们尺寸的路径。通过把 innodb_data_home_dir 连接到这里指定的每个路径,到每个数据文件的完整目录路径可被获得。文件大小通过给尺寸值尾加 M 或 G 以 MB 或者 GB(1024MB) 为单位被指定。文件尺寸的和至少是 10MB。在一些操作系统上,文件必须小于 2GB。如果你没有指定 innodb_data_file_path,开始的默认行为是创建一个单独的大小 10MB 名为 ibdata1 的自扩展数据文件。在那些支持大文件的操作系统上,你可以设置文件大小超过 4GB。你也可以使用原始磁盘分区作为数据文件,请参阅15.2.14.2 节,“为表空间使用原始设备”

·         innodb_data_home_dir

目录路径对所有 InnoDB 数据文件的共同部分。如果你不设置这个值, 默认是 MySQL 数据目录。你也可以指定这个值为一个空字符串,在这种情况下,你可以在 innodb_data_file_path 中使用绝对文件路径。

·         innodb_doublewrite

默认地,InnoDB 存储所有数据两次,第一次存储到 doublewrite 缓冲,然后存储到确实的数据文件。这个选项可以被用来禁止这个功能。类似于 innodb_checksums,这个选项 默认是允许的;因为标准检查或在对顶级性能的需要超过对数据完整性或可能故障的关注之时,这个选项用--skip-innodb-doublewrite 来关闭。

·         innodb_fast_shutdown

如果你把这个参数设置为 0,InnoDB 在关闭之前做一个完全净化和一个插入缓冲合并。这些操作要花几分钟时间,设置在极端情况下要几个小时。如果你设置这个参数为 1,InnoDB 在关闭之时跳过这些操作。 默认值为 1。如果你设置这个值为 2 (在 Netware 无此值), InnoDB 将刷新它的日志然后冷关机,仿佛 MySQL 崩溃一样。已提交的事务不会被丢失,但在下一次启动之时会做一个崩溃恢复。

·         innodb_file_io_threads

InnoDB 中文件 I/O 线程的数量。正常地,这个参数是用 默认的,默认值是 4,但是大数值对 Windows 磁盘 I/O 有益。在 Unix 上,增加这个数没有效果,InnoDB 总是使用默认值。

·         innodb_file_per_table

这个选项致使 InnoDB 用自己的。ibd 文件为存储数据和索引创建每一个新表,而不是在共享表空间中创建。请参阅15.2.6.6 节,“使用 Per-Table 表空间”

·         innodb_flush_log_at_trx_commit

当 innodb_flush_log_at_trx_commit 被 设置为 0,日志缓冲每秒一次地被写到日志文件,并且对日志文件做到磁盘操作的刷新,但是在一个事务提交不做任何操作。当这个值为 1(默认值) 之时,在每个事务提交时,日志缓冲被写到日志文件,对日志文件做到磁盘操作的 刷新。当设置为 2 之时,在每个提交,日志缓冲被写到文件,但不对日志文件做到磁盘操作的刷新。尽管如此,在对日志文件的刷新在值为 2 的情况也每秒发生一次。我们必须注意到,因为进程安排问题,每秒一次的 刷新不是 100% 保证每秒都发生。你可以通过设置这个值不为 1 来获得较好的性能,但随之你会在一次崩溃中损失二分之一价值的事务。如果你设置这个值为 0,那么任何mysqld进程的崩溃会删除崩溃前最后一秒的事务,如果你设置这个值为 2,那么只有操作系统崩溃或掉电才会删除最后一秒的事务。尽管如此,InnoDB 的崩溃恢复不受影响,而且因为这样崩溃恢复开始作用而不考虑这个值。注意,许多操作系统和一些磁盘硬件会欺骗 刷新到磁盘操作。尽管刷新没有进行,你可以告诉mysqld刷新已经进行。即使设置这个值为 1,事务的持久程度不被保证,且在最坏情况下掉电甚至会破坏 InnoDB 数据库。在 SCSI 磁盘控制器中,或在磁盘自身中,使用有后备电池的磁盘缓存会加速文件 刷新并且使得操作更安全。你也可以试着使用 Unix 命令hdparm来在硬件缓存中禁止磁盘写缓存,或使用其它一些对硬件提供商专用的命令。这个选项的 默认值是 1。

·         innodb_flush_method

这个选项只在 Unix 系统上有效。如果这个选项被设置为fdatasync (默认值),InnoDB 使用 fsync() 来刷新数据和日志文件。如果被设置为 O_DSYNC,InnoDB 使用 O_SYNC 来打开并刷新日志文件,但使用 fsync() 来 刷新数据文件。如果 O_DIRECT 被指定了 (在一些 GNU/Linux 版本商可用),InnoDB 使用 O_DIRECT 来打开数据文件,并使用 fsync() 来刷新数据和日志文件。注意,InnoDB 使用 fsync() 来替代 fdatasync(),并且它 默认不使用 O_DSYNC,因为这个值在许多 Unix 变种上已经发生问题。

·         innodb_force_recovery

警告:这个选项仅在一个紧急情况下被定义,当时你想要从损坏的数据库转储表。可能的值为从 1 到 6。这些值的意思在15.2.8.1 节,“强制恢复”中叙述。作为一个安全措施,当这个选项值大于零之时,InnoDB 阻止用户修改数据。

·         innodb_lock_wait_timeout

InnoDB 事务在被回滚之前可以等待一个锁定的超时秒数。InnoDB 在它自己的 锁定表中自动检测事务死锁并且回滚事务。InnoDB 用 LOCK TABLES 语句注意到锁定设置。默认值是 50 秒。

为在一个复制建立中最大可能的持久程度和连贯性,你应该在主服务器上的 my.cnf 文件里使用 innodb_flush_log_at_trx_commit=1 和 sync-binlog=1。

·         innodb_locks_unsafe_for_binlog

这个选项在 InnoDB 搜索和索引扫描中关闭下一键锁定。这个选项的 默认值是假 (false)。

正常地,InnoDB 使用一个被称为next-key locking的算法。当搜索或扫描一个表索引之时,InnoDB 以这样一种方式实行行级锁定,它对任何遇到的索引记录设置共享的或独占的锁定。因此,行级锁定实际是索引记录锁定。InnoDB 对索引记录设置的锁定也影响被锁定索引记录之前的“gap”。如果一个用户对某一索引内的记录R又共享的或独占的锁定,另一个用户不能立即在R之前以索引的顺序插入一个新的索引记录。这个选项导致 InnoDB 不在搜索或索引扫描中使用下一 键锁定。下一键锁定仍然被用来确保外键强制及重复键核查。注意,使用这个选项可能会导致一些诡异的问题:假设你想要用值大于 100 的标识符从子表里读取并锁定所有的子记录,同时 向随后在选定的行更新一些列:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

假设在id 列有一个索引。查询从 id 大于 100 的第一个记录开始扫描索引。如果在索引记录上的锁定不把在间隙处生成的插入排除锁定,同时一个新行被插进表中。如果你在同一个事务之内执行同样的 SELECT,你会在查询返回的结果包里看到一个新行。这也意味着,如果新条目被加进数据库,InnoDB 不保证连续性;尽管如此, 对应连续性仍被保证。因此,如果这个选项被使用,InnoDB 在大多数孤立级别保证 READ COMMITTED。

这个选项甚至更不安全。InnoDB 在一个 UPDATE 或 DELETE 中只锁定它更新或删除的行。这大大减少了死锁的可能性,但是可以发生死锁。注意,即使在当类似的操作影响不同行时的情况下,这个选项仍然不允许诸如 UPDATE 这样的操作压倒相似选项 (比如另一个 UPDATE)。考虑下列例子:

CREATE TABLE A(A INT NOT NULL, B INT);
INSERT INTO A VALUES (1,2),(2,3),(3,2),(4,3),(5,2);
COMMIT;

如果一个连接执行一个查询:

SET AUTOCOMMIT = 0;
UPDATE A SET B = 5 WHERE B = 3;

并且其它连接跟着第一个连接执行其它查询:

SET AUTOCOMMIT = 0;
UPDATE A SET B = 4 WHERE B = 2;

接着查询 2 要等查询 1 的提交或回滚,因为查询 1 对行 (2,3) 有一个独占的锁定,并且查询 2 在扫描行的同时也试着对它不能锁定的同一个行 (2,3) 采取一个独占的锁定。这是因为当 innodb_locks_unsafe_for_binlog 选项被使用之时,查询 2 首先对一个行采取一个独占的锁定,然后确定是否这个行属于结果包,并且如果不属于,就释放不必要的锁定。

因此,查询 1 按如下执行:

x-lock(1,2)
unlock(1,2)
x-lock(2,3)
update(2,3) to (2,5)
x-lock(3,2)
unlock(3,2)
x-lock(4,3)
update(4,3) to (4,5)
x-lock(5,2)
unlock(5,2)

并且查询 2 按如下执行:

x-lock(1,2)
update(1,2) to (1,4)
x-lock(2,3) - 等待查询 1 提交或回滚

·         innodb_log_arch_dir

如果我们使用日志档案,被完整写入的日志文件所在的目录也被归档。这个参数值如果被使用了,应该被设置得与innodb_log_group_home_dir一样。尽管如此,它不是必需的。

·         innodb_log_archive

这个值当前被设为 0。因为 MySQL 使用它自己的日志文件从备份来恢复,所以当前没有必要来归档 InnoDB 日志文件。这个选项的 默认值是 0。

·         innodb_log_buffer_size

InnoDB 用来往磁盘上的日志文件写操作的缓冲区的大小。明智的值是从 1MB 到 8MB。 默认的是 1MB。一个大的日志缓冲允许大型事务运行而不需要在事务提交之前往磁盘写日志。因此,如果你有大型事务,使日志缓冲区更大以节约磁盘 I/O。

·         innodb_log_file_size

在日志组里每个日志文件的大小。在 32 位计算机上日志文件的合并大小必须少于 4GB。 默认是 5MB。明智的值从 1MB 到N分之一缓冲池大小,其中N是组里日志文件的数目。值越大,在缓冲池越少需要检查点刷新行为,以节约磁盘 I/O。但更大的日志文件也意味这在崩溃时恢复得更慢。

·         innodb_log_files_in_group

在日志组里日志文件的数目。InnoDB 以循环方式写进文件。默认是 2(推荐)。

·         innodb_log_group_home_dir

到 InnoDB 日志文件的目录路径。它必须有和 innodb_log_arch_dir 一样的值。如果你不指定任何 InnoDB 日志参数, 默认的是在 MySQL 数据目录里创建两个 5MB 大小名为 ib_logfile0 和 ib_logfile1 的文件。

·         innodb_max_dirty_pages_pct

这是一个范围从 0 到 100 的整数。默认是 90。InnoDB 中的主线程试着从缓冲池写页面,使得脏页 (没有被写的页面) 的百分比不超过这个值。如果你有 SUPER 权限,这个百分比可以在服务器运行时按下面来改变:

SET GLOBAL innodb_max_dirty_pages_pct = value;

·         innodb_max_purge_lag

这个选项控制在净化操作被滞后之时,如何延迟 INSERT, UPDATE 和 DELETE 操作。(请参阅15.2.12 节,“多版本的实施”)。这个参数的 默认值是零,意为无延迟。这个选项可以在运行时作为全局系统变量而被改变。

InnoDB 事务系统维持一个事务列表,该列表有被 UPDATE 或 DELETE 操作标志为删除的索引记录。让这个列表的长度为purge_lag。当purge_lag超过 innodb_max_purge_lag 之时,每个 INSERT, UPDATE 和 DELETE 操作延迟 ((purge_lag/innodb_max_purge_lag)*10)-5 毫秒。在净化批处理的开始,延迟每隔 10 秒计算。如果因为一个旧的可以看到行被净化的一致的读查看, 删除操作不被延迟。

对有问题的工作量,典型设置可能是 1 百万,假设我们的事务很小,只有 100 字节大小,我们就可以允许在我们的表之中有 100MB 未净化的行。

·         innodb_mirrored_log_groups

我们为数据库保持的日志组内同样拷贝的数量。当前这个值应该被设为 1。

·         innodb_open_files

在 InnoDB 中,这个选项仅与你使用多表空间时有关。它指定 InnoDB 一次可以保持打开的。ibd 文件的最大数目。最小值是 10。 默认值 300。

对。ibd 文件的文件描述符是仅对 InnoDB 的。它们独立于那些由--open-files-limit 服务器选项指定的描述符,且不影响表缓存的操作。

·         innodb_status_file

这个选项让 InnoDB 为周期的 SHOW INNODB STATUS 输出创建一个文件<datadir>/innodb_status.<pid>

·         innodb_support_xa

当被设置为 ON 或者 1(默认地),这个变量允许 InnoDB 支持在 XA 事务中的 双向提交。允许 innodb_support_xa 导致一个额外的对事务准备的磁盘刷新。如果你对使用 XA 并不关心,你可以通过设置这个选项为 OFF 或 0 来禁止这个变量,以减少磁盘 刷新的次数并获得更好的 InnoDB 性能。

·         innodb_table_locks

InnoDB 重视 LOCK TABLES,直到所有其它线程已经释放他们所有对表的锁定,MySQL 才从 LOCK TABLE .. WRITE 返回。默认值是 1,这意为 LOCK TABLES 让 InnoDB 内部锁定一个表。在使用 AUTOCOMMIT=1 的应用里,InnoDB 的内部表锁定会导致死锁。你可以在 my.cnf 文件 (Windows 上是 my.ini 文件) 里设置 innodb_table_locks=0 来 消除这个问题。

·         innodb_thread_concurrency

InnoDB 试着在 InnoDB 内保持操作系统线程的数量少于或等于这个参数给出的限制。如果有性能问题,并且 SHOW INNODB STATUS 显示许多线程在等待信号,可以让线程“thrashing” ,并且设置这个参数更小或更大。如果你的计算机有多个处理器和磁盘,你可以试着这个值更大以更好地利用计算机的资源。一个推荐的值是系统上处理器和磁盘的个数之和。值为 500 或比 500 大会禁止 调用并发检查。默认值是 20,并且如果设置大于或等于 20,并发检查将被禁止。

·         innodb_status_file

这个选项让 InnoDB 为周期的 SHOW INNODB STATUS 输出创建一个文件<datadir>/innodb_status.<pid>

15.2.5. ;创建InnoDB表空间

15.2.5.1. 处理InnoDB 初始化问题

假设你已经安装了 MySQL,并且已经编辑了选项文件,使得它包含必要的 InnoDB 配置参数。在启动 MySQL 之前,你应该验证你为 InnoDB 数据文件和日志文件指定的目录是否存在,并且 MySQL 有访问这些目录的权限。InnoDB 不能创建目录,只能创建文件。也检查你有足够的空间来放数据和日志文件。

当创建 InnoDB 数据库时,最好从命令提示符运行 MySQL 服务器mysqld, 而不要从mysqld_safe包装或作为 Windows 的服务来运行。当你从命令提示符运行,你可看见mysqld打印什么以及发生了什么。在 Unix 上,只需要调用mysqld。在 Windows 上,使用--console 选项。

当在选项文件里初始地配置 InnoDB 后,开始启动 MySQL 服务器之时,InnoDB 创建一个数据文件和日志文件。InnoDB 打印如下一些东西:

InnoDB: The first specified datafile /home/heikki/data/ibdata1
did not exist:
InnoDB: a new database to be created!
InnoDB: Setting file /home/heikki/data/ibdata1 size to 134217728
InnoDB: Database physically writes the file full: wait...
InnoDB: datafile /home/heikki/data/ibdata2 did not exist:
new to be created
InnoDB: Setting file /home/heikki/data/ibdata2 size to 262144000
InnoDB: Database physically writes the file full: wait...
InnoDB: Log file /home/heikki/data/logs/ib_logfile0 did not exist:
new to be created
InnoDB: Setting log file /home/heikki/data/logs/ib_logfile0 size
to 5242880
InnoDB: Log file /home/heikki/data/logs/ib_logfile1 did not exist:
new to be created
InnoDB: Setting log file /home/heikki/data/logs/ib_logfile1 size
to 5242880
InnoDB: Doublewrite buffer not found: creating new
InnoDB: Doublewrite buffer created
InnoDB: Creating foreign key constraint system tables
InnoDB: Foreign key constraint system tables created
InnoDB: Started
mysqld: ready for connections

一个新的 InnoDB 数据库被创建了。你可以用mysql这样通常的 MySQL 客户端程序连接到 MySQL 服务器。当你用mysqladmin shutdown关闭 MySQL 服务器之时,输出类似如下:

010321 18:33:34  mysqld: Normal shutdown
010321 18:33:34  mysqld: Shutdown Complete
InnoDB: Starting shutdown...
InnoDB: Shutdown completed

你可以看数据文件和日志文件,并且你可以看见文件被创建。日志目录也包含一个名为 ib_arch_log_0000000000 的小文件。这个文件是数据库被创建的结果,数据库被创建之后 InnoDB 切断日志归档。当 MySQL 再次启动之时,数据文件&日志文件已经被创建,所以输出更简洁:

InnoDB: Started
mysqld: ready for connections

你可以添加 innodb_file_per_table 选项到 my.cnf 文件,并且让 InnoDB 存储每一个表到 MySQL 数据库目录里自己的。ibd 文件。请参阅15.2.6.6 节,“使用 Per-Table 表空间”

15.2.5.1. ;处理 InnoDB 初始化问题

如果 InnoDB 在一个文件操作中打印一个操作系统错误,通常问题是如下中的一个:

·         你没有创建一个 InnoDB 数据文件目录或 InnoDB 日志目录。

·         mysqld没有访问这些目录的权限 以创建文件。

·         mysqld不能恰当地读取 my.cnf 或 my.ini 选项文件,因此不能看到你指定的选项。

·         磁盘已满,或者超出磁盘配额。

·         你已经创建一个子目录,它的名字与你指定的数据文件相同。

·         在 innodb_data_home_dir 或 innodb_data_file_path 有一个语法错误。

当 InnoDB 试着初始化它的表空间或日志文件之时,如果出错了,你应该删除 InnoDB 创建的所有文件。这意味着是所有 ibdata 文件和所有 ib_logfiles 文件。万一你创建了一些 InnoDB 表,为这些表也从 MySQL 数据库目录删除相应的。frm 文件 (如果你使用多重表空间的话,也删除任何。ibd 文件)。然后你可以试着再次创建 InnoDB 数据库。最好是从命令提示符启动 MySQL 服务器 ,以便你可以查看发生了什么。

15.2.6. ;创建InnoDB

15.2.6.1. 如何在InnoDB 用不同 API 来使用事务

15.2.6.2. 转换MyISAM 表到 InnoDB

15.2.6.3. AUTO_INCREMENT列如何在InnoDB中工作

15.2.6.4.外键约束

15.2.6.5. InnoDBMySQL 复制

15.2.6.6. 使用Per-Table 表空间

假如你用 mysql test 命令启动 MySQL 客户端。要创建一个 InnoDB 表,你必须在表创建 SQL 语句中指定 ENGINE = InnoDB 或者 TYPE = InnoDB 选项:

CREATE TABLE customers (a INT, b CHAR (20), INDEX (a)) ENGINE=InnoDB;
CREATE TABLE customers (a INT, b CHAR (20), INDEX (a)) TYPE=InnoDB;

SQL 语句在表空间的列上创建一个表和索引,表空间包含你在 my.cnf 指定的数据文件。此外,MySQL 在 MySQL 数据库目录下的 test 目录里创建一个名为 customers.frm 的文件。内部地,InnoDB 为'test/customers'表往自己的数据目录添加一个条目。这意味这你可以在其它数据库创建一个具有相同名字 customers 的表,表的名字不会与 InnoDB 内的冲突。

你可以对任何 InnoDB,通过使用SHOW TABLE STATUS语句,查询在InnoDB表空间内空闲空间的数量。表空间内空闲空间的数量出现在SHOW TABLE STATUS的输出结果内的Comment节里。例如:

SHOW TABLE STATUS FROM test LIKE 'customers'

注意,统计的 SHOW 只给出关于 InnoDB 表的大概情况。它们被用于 SQL 优化。可是,表和索引保留的大小,以字节为单位是准确的。

15.2.6.1. ;如何在 InnoDB 中用不同的 API 来使用事务

默认地,每个连接到 MySQL 服务器的客户端开始之时是允许自动提交模式的,这个模式自动提交你运行的每个 SQL 语句。要使用多语句事务,你可以用 SQL 语句 SET AUTOCOMMIT = 0 禁止自动提交,并且用 COMMIT 和 ROLLBACK 来提交或回滚你的事务。 如果你想要 autocommit 保持打开状态,可以在 START TRANSACTION 与 COMMIT 或 ROLLBACK 之间封装你的事务。下列的例子演示两个事务。第一个是被提交的,第二个是被 回滚的:

shell> mysql test
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5 to server version: 3.23.50-log
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> CREATE TABLE CUSTOMER (A INT, B CHAR (20), INDEX (A))
    -> ENGINE=InnoDB;
Query OK, 0 rows affected (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO CUSTOMER VALUES (10, 'Heikki');
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.00 sec)
mysql> SET AUTOCOMMIT=0;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO CUSTOMER VALUES (15, 'John');
Query OK, 1 row affected (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM CUSTOMER;
+------+--------+
| A    | B      |
+------+--------+
|   10 | Heikki |
+------+--------+
1 row in set (0.00 sec)
mysql>

在类似 PHP, Perl DBI/DBD, JDBC, ODBC,或者 MySQL 的标准 C 调用接口这样的 API 上,你能够以字符串形式发送事务控制语句,如 COMMIT,到 MySQL 服务器,就像其它任何的 SQL 语句 那样,诸如 SELECT 或 INSERT。一些 API 也提供单独的专门的事务提交和回滚函数或者方法。

15.2.6.2. ;转换 MyISAM 表到 InnoDB

要点:你不应该在 mysql 数据库 (比如,user 或者 host) 里把 MySQL 系统表转换为 InnoDB 类型。系统表总是 MyISAM 型。

如果你想要所有 (非系统) 表都被创建成 InnoDB 表,你可以简单地把 default-table-type=innodb 行添加到 my.cnf 或 my.ini 文件的 [mysqld] 节里。

InnoDB 对 MyISAM 存储引擎采用的单独索引创建方法没有做专门的优化。因此,它不值得导出或导入表以及随后创建索引。改变一个表为 InnoDB 型最快的办法就是直接插入进一个 InnoDB 表。即,使用 ALTER TABLE ... ENGINE=INNODB,或用相同的定义创建一个空 InnoDB 表,并且用 INSERT INTO ... SELECT * FROM ...插入行。

如果你对第二个键有 UNIQUE 约束,你可以在导入阶段设置:SET UNIQUE_CHECKS=0,以临时关掉唯一性检查好加速表的导入。对于大表,这节省了大量的磁盘 I/O,因为 InnoDB 随后可以使用它的插入缓冲区来第二个索引记录作为一批来写入。

为获得对插入进程的更好控制,分段插入大表可能比较好:

INSERT INTO newtable SELECT * FROM oldtable
   WHERE yourkey > something AND yourkey <= somethingelse;

所有记录已经本插入之后,你可以重命名表。

在大表的转换中,你应该增加 InnoDB 缓冲池的大小来减少磁盘 I/O。尽管如此,不要使用超过 80% 的内部内存。你也可以增加 InnoDB 日志文件和日志文件的大小。

确信你没有填满表空间:InnoDB 表比 MyISAM 表需要大得多的磁盘空间。如果一个 ALTER TABLE 耗尽了空间,它就开始一个 回滚,并且如果它是磁盘绑定的,回滚可能要几个小时。对于插入,InnoDB 使用插入缓冲区来以成批地合并第二个索引记录到索引中。那样节省了大量磁盘 I/O。在回滚中,没有使用这样的机制,而回滚要花比插入长 30 倍的时间来完成。

在失控的回滚情况下,如果你在数据库中没有有价值的数据,比较明智的是杀掉数据库进程而不是等几百万个磁盘 I/O 被完成。 完整的过程,请参阅15.2.8.1 节,“强制恢复”

15.2.6.3. AUTO_INCREMENT 列在 InnoDB 里如何工作

如果你为一个表指定 AUTO_INCREMENT 列,在数据词典里的 InnoDB 表句柄包含一个名为自动增长计数器的计数器,它被用在为该 列赋新值。自动增长计数器仅被存储在主内存中,而不是存在磁盘上。

InnoDB 使用下列算法来为包含一个名为 ai_col 的 AUTO_INCREMENT 列的表 T 初始化自动增长计数器:服务器启动之后,当一个用户对表 T 做插入之时,InnoDB 执行等价如下语句的动作:

SELECT MAX(ai_col) FROM T FOR UPDATE;

语句取回的值逐次加一,并被赋给列和自动增长计数器。如果表是空的,值 1 被赋予该列。如果自动增长计数器没有被初始化,而且用户调用为表 T 显示输出的 SHOW TABLE STATUS 语句,则计数器被初始化 (但不是增加计数) 并被存储以供随后的插入使用。注意,在这个初始化中,我们对表做一个正常的独占锁定,这个锁持续到事务的结束。

InnoDB 对为新创建表的初始化自动增长计数器允许同样的过程。

注意,如果用户在 INSERT 中为 AUTO_INCREMENT 列指定 NULL 或者 0,InnoDB 处理行,就仿佛值还没有被指定,且为它生成一个新值。

自动增长计数器被初始化之后,如果用户插入一个明确指定该列值的行,而且该值大于当前计数器值,则计数器被设置为指定 列值。如果没有明确指定一个值,InnoDB 给计数器增加一,并且赋新值给该列。

当访问自动增长计数器之时,InnoDB 使用专用的表级的 AUTO-INC 锁定,该锁持续到当前 SQL 语句的结束而不是到业务的结束。 引入了专用锁释放策略,来为对一个含 AUTO_INCREMENT 列的表的插入改善部署。两个事务不能同时对同一表有 AUTO-INC 锁定。

注意,如果你回滚从计数器获得数的事务,你可能会在赋给 AUTO_INCREMENT 列的值的序列中发现间隙。

如果用户给列赋一个赋值,或者,如果值大过可被以指定整数格式存储的最大整数,自动增长机制的行为不被定义。

在 CREATE TABLE 和 ALTER TABLE 语句中,InnoDB 支持 AUTO_INCREMENT = n 表选项来设置计数器初始值或变更当前计数器值。因在本节早先讨论的原因,这个选项的影响在服务器重启后就无效了。

15.2.6.4.外键约束

InnoDB 也支持外键约束。InnoDB 中对外键约束定义的语法看起来如下:

[CONSTRAINT symbol] FOREIGN KEY [id] (index_col_name, ...)
    REFERENCES tbl_name (index_col_name, ...)
    [ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION}]
    [ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION}]

外键定义服从下列情况:

·         所有 tables 必须是 InnoDB 型,它们不能是临时表。

·         在引用表中,必须有一个索引,外键列以同样的顺序被列在其中作为第一列。这样一个索引如果不存在,它必须在 引用表里被自动创建。

·         在引用表中,必须有一个索引,被引用的列以同样的顺序被列在其中作为第一列。

·         不支持对外键列的索引前缀。这样的后果之一是 BLOB 和 TEXT 列不被包括在一个外键中,这是因为对这些列的索引必须总是包含一个前缀长度。

·         如果 CONSTRAINTsymbol被给出,它在数据库里必须是唯一的。如果它没有被给出,InnoDB 自动创建这个名字。

InnoDB 拒绝任何试着在子表创建一个外键值而不匹配在父表中的候选键值的 INSERT 或 UPDATE 操作。一个父表有一些匹配的行 的子表,InnoDB 对任何试图更新或删除该父表中候选键值的 UPDATE 或 DELETE 操作有所动作,这个动作取决于用 FOREIGN KEY 子句的 ON UPDATE 和 ON DETETE 子句指定的referential action。当用户试图从一个父表删除或更新一行之时,且在子表中有一个或多个匹配的行,InnoDB 根据要采取的动作有五个选择:

·         CASCADE: 从父表删除或更新且自动删除或更新子表中匹配的行。ON DELETE CASCADE 和 ON UPDATE CASCADE 都可用。在两个表之间,你不应定义若干在父表或子表中的同一列采取动作的 ON UPDATE CASCADE 子句。

·         SET NULL: 从父表删除或更新行,并设置子表中的外键列为 NULL。如果外键列没有指定 NOT NULL 限定词,这就是唯一合法的。ON DELETE SET NULL 和 ON UPDATE SET NULL 子句被支持。

·         NO ACTION:在 ANSI SQL-92 标准中,NO ACTION 意味这不采取动作,就是如果有一个相关的外键值在被参考的表里,删除或更新主要键值的企图不被允许进行 (Gruber, 掌握 SQL, 2000:181)。 InnoDB 拒绝对父表的删除或更新操作。

·         RESTRICT:拒绝对父表的删除或更新操作。NO ACTION 和 RESTRICT 都一样,删除 ON DELETE 或 ON UPDATE 子句。(一些数据库系统有延期检查,并且 NO ACTION 是一个延期检查。在 MySQL 中,外键约束是被立即检查的,所以 NO ACTION 和 RESTRICT 是同样的)。

·         SET DEFAULT: 这个动作被解析程序识别,但 InnoDB 拒绝包含 ON DELETE SET DEFAULT 或 ON UPDATE SET DEFAULT 子句的表定义。

当父表中的候选键被更新的时候,InnoDB支持同样选择。选择 CASCADE,在子表中的外键 列被设置为父表中候选键的新值。以同样的方式,如果在子表更新的列参考在另一个表中的外键,更新级联。

注意,InnoDB 支持外键在一个表内引用,在这些情况下,子表实际上意味这在表内附属的记录。

InnoDB 需要对外键和被引用键的索引以便外键检查可以快速进行且不需要一个表扫描。对外键的索引被自动创建。这是相对于一些老版本,在老版本中索引必须明确创建,否则外键约束的创建会失败。

在 InnoDB 内,外键里和被引用列里相应的列必须有类似的内部数据类型,以便它们不需类型转换就可被比较。整数类型的大小和符号必须相同。字符串类型的长度不需要相同。如果你指定一个 SET NULL 动作,请确认你没有在子表中宣告该 列为为NOT NULL

如果 MySQL 从 CREATE TABLE 语句报告一个错误号 1005,并且错误信息字符串指向 errno 150,这意思是因为一个外键约束被不正确形成,表创建失败。类似地,如果 ALTER TABLE 失败,且它指向 errno 150, 那意味着对已变更的表,外键定义会被不正确的形成。你可以使用 SHOW INNODB STATUS 来显示一个对服务器上最近的 InnoDB 外键错误的详细解释。

注释:InnoDB 不对那些 外键或包含 NULL 列的被引用键值检查外键约束。

对 SQL 标准的背离:如果在父表内有数个行,其中有相同的 被引用键值,然后 InnoDB 在外键检查中采取动作,就仿佛其它有相同键值的父行不存在一样。例如,如果你已定义一个 RESTRICT 类型的约束,并且有一个带数个父行的子行,InnoDB 不允许任何对这些父行的删除。

居于对应外键约束的索引内的记录,InnoDB 通过深度优先选法施行级联操作。

对 SQL 标准的背离: 如果 ON UPDATE CASCADE 或 ON UPDATE SET NULL 递归更新相同的表,之前在级联过程中该表一被更新过,它就象 RESTRICT 一样动作。这意味着你不能使用自引用 ON UPDATE CASCADE 或者 ON UPDATE SET NULL 操作。这将阻止级联更新导致的无限循环。另一方面,一个自引用的 ON DELETE SET NULL 是有可能的,就像一个自引用 ON DELETE CASCADE 一样。 级联操作不可以被嵌套超过 15 层深。

对 SQL 标准的背离: 类似一般的 MySQL,在一个插入,删除或更新许多行的 SQL 语句内,InnoDB 逐行检查 UNIQUE 和 FOREIGN KEY 约束。按照 SQL 的标准, 默认的行为应被延迟检查,即约束仅在整个 SQL 语句被处理之后才被检查。直到 InnoDB 实现延迟的约束检查之前,一些事情是不可能的,比如删除一个通过外键参考到自身的记录。

注释:当前,触发器不被级联外键的动作激活。

一个通过单列外键联系起父表和子表的简单例子如下:

CREATE TABLE parent(id INT NOT NULL,
                    PRIMARY KEY (id)
) TYPE=INNODB;
CREATE TABLE child(id INT, parent_id INT,
                   INDEX par_ind (parent_id),
                   FOREIGN KEY (parent_id) REFERENCES parent(id)
                     ON DELETE CASCADE
) TYPE=INNODB;

如下是一个更复杂的例子,其中一个 product_order 表对其它两个表有外键。一个外键引用一个 product 表中的双列索引。另一个 引用在 customer 表中的单行索引:

CREATE TABLE product (category INT NOT NULL, id INT NOT NULL,
                      price DECIMAL,
                      PRIMARY KEY(category, id)) TYPE=INNODB;
CREATE TABLE customer (id INT NOT NULL,
                      PRIMARY KEY (id)) TYPE=INNODB;
CREATE TABLE product_order (no INT NOT NULL AUTO_INCREMENT,
                      product_category INT NOT NULL,
                      product_id INT NOT NULL,
                      customer_id INT NOT NULL,
                      PRIMARY KEY(no),
                      INDEX (product_category, product_id),
                      FOREIGN KEY (product_category, product_id)
                        REFERENCES product(category, id)
                        ON UPDATE CASCADE ON DELETE RESTRICT,
                      INDEX (customer_id),
                      FOREIGN KEY (customer_id)
                        REFERENCES customer(id)) TYPE=INNODB;

InnoDB允许你用 ALTER TABLE 往一个表中添加一个新的 外键约束:

ALTER TABLE yourtablename
    ADD [CONSTRAINT symbol] FOREIGN KEY [id] (index_col_name, ...)
    REFERENCES tbl_name (index_col_name, ...)
    [ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION}]
    [ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION}]

记住先创建需要的索引。你也可以用 ALTER TABLE 往一个表添加一个自引用外键约束。

InnoDB 也支持使用ALTER TABLE 来移除 外键:

ALTER TABLE yourtablename DROP FOREIGN KEY fk_symbol;

当年创建一个外键之时,如果 FOREIGN KEY 子句包括一个 CONSTRAINT 名字,你可以引用那个名字来移除 外键。另外,当外键被创建之时,fk_symbol 值被 InnoDB 内部保证。当你想要移除一个外键之时,要找出标记,请使用 SHOW CREATE TABLE 语句。例子如下:

mysql> SHOW CREATE TABLE ibtest11c\G
*************************** 1. row ***************************
       Table: ibtest11c
Create Table: CREATE TABLE `ibtest11c` (
  `A` int(11) NOT NULL auto_increment,
  `D` int(11) NOT NULL default '0',
  `B` varchar(200) NOT NULL default '',
  `C` varchar(175) default NULL,
  PRIMARY KEY  (`A`,`D`,`B`),
  KEY `B` (`B`,`C`),
  KEY `C` (`C`),
  CONSTRAINT `0_38775` FOREIGN KEY (`A`, `D`)
REFERENCES `ibtest11a` (`A`, `D`)
ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `0_38776` FOREIGN KEY (`B`, `C`)
REFERENCES `ibtest11a` (`B`, `C`)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=INNODB CHARSET=latin1
1 row in set (0.01 sec)
 
mysql> ALTER TABLE ibtest11c DROP FOREIGN KEY 0_38775;

InnoDB 解析程序允许你在 FOREIGN KEY ... REFERENCES ...子句中用`(backticks) 把表和列名名字围起来。InnoDB 解析程序也考虑到 lower_case_table_names 系统变量的设置。

InnoDB返回一个表的外键定义作为 SHOW CREATE TABLE 语句输出的一部分:

SHOW CREATE TABLE tbl_name;

从这个版本起,mysqldump也将表的正确定义生成到转储文件中,且并不忘记 外键。

你可以如下对一个表显示外键约束:

SHOW TABLE STATUS FROM db_name LIKE 'tbl_name';

外键约束被列在输出的 Comment 列。

当执行外键检查之时,InnoDB 对它照看着的子或父记录设置共享的行级锁。InnoDB 立即检查外键约束,检查不对事务提交延迟。

要使得对有外键关系的表重新载入转储文件变得更容易,mysqldump自动在转储输出中包括一个语句设置 FOREIGN_KEY_CHECKS 为 0。这避免在转储被重新装载之时,与不得不被以特别顺序重新装载的表相关的问题。也可以手动设置这个变量:

mysql> SET FOREIGN_KEY_CHECKS = 0;
mysql> SOURCE dump_file_name;
mysql> SET FOREIGN_KEY_CHECKS = 1;

如果转储文件包含对外键是不正确顺序的表,这就以任何顺序导入该表。这样也加快导入操作。设置 FOREIGN_KEY_CHECKS 为 0,对于在 LOAD DATA 和 ALTER TABLE 操作中忽略外键限制也是非常有用的。

InnoDB不允许你删除一个被 FOREIGN KEY 表约束 引用的表,除非你做设置 SET FOREIGN_KEY_CHECKS=0。当你移除一个表的时候,在它的创建语句里定义的约束也被移除。

如果你重新创建一个被移除的表,它必须有一个遵从于也引用它的外键约束的定义。它必须有正确的列名和类型,并且如前所述,它必须对被 引用的键有索引。如果这些不被满足,MySQL 返回错误号 1005 并在错误信息字符串中指向 errno 150。

15.2.6.5. InnoDB 和 MySQL 复制

MySQL 复制就像对 MyISAM 表一样,也对 InnoDB 表起作用。以某种方式使用复制也是可能的,在这种方式中从服务器上表的类型不同于主服务器上 原始表的类型。例如,你可以复制修改到主服务器上一个 InnoDB 表,到从服务器上一个 MyISAM 表里。

要为一个主服务器建立一个新服务器,你不得不复制 InnoDB 表空间和日志文件,以及 InnoDB 表的。frm 文件,并且移动复件到从服务器。 关于其恰当步骤请参阅15.2.9 节,“移动 InnoDB 数据库到另一台机器”

如果你可以关闭主服务器或者一个存在的从服务器。你可以采取 InnoDB 表空间和日志文件的冷备份,并用它来建立一个从服务器。要不关闭任何服务器来建立一个新的从服务器,你也可以使用非免费 (商业的)InnoDB 热备份工具

InnoDB 复制里一个小的限制是 LOAD TABLE FROM MASTER 不对 InnoDB 类型表起作用。有两种可能的工作区:

·         转储主服务器上的表并导入转储文件到从服务器。

·         在用 LOAD TABLE tbl_name FROM MASTER 建立复制之前,在主服务器上使用 ALTER TABLE tbl_name TYPE=MyISAM,并且随后使用 ALTER TABLE 把主表转换回 InnoDB。

在主服务器失败的事务根本不影响复制。MySQL 复制基于二进制日志,MySQ 在那里写修改数据的 SQL 语句。从服务器读主服务器的二进制日志,并执行同样的 SQL 语句。尽管如此,在事务里发生的语句不被写进二进制日志直到事务提交,在那一刻,事务里所有语句被一次性写进日志。如果语句失败了,比如,因为外键违例,或者,如果事务被回滚,没有 SQL 语句被写进二进制日志,并且事务根本不在从服务器上执行。

15.2.6.6. ;使用 Per-Table 表空间

你可以存储每个 InnoDB 表和它的索引在它自己的文件在中,这个特征被称为&ldquo;多表空间” ,因为实际上每个表有它自己的表空间。

对那些想把特定表格移到分离物理磁盘的用户,或者那些希望快速恢复单个表的备份而无须打断其余 InnoDB 表的使用的用户,使用多表空间会是有益的。

你可以往 my.cnf 的 [mysqld] 节添加下面行来允许多表空间:

[mysqld]
innodb_file_per_table

重启服务器之后,InnoDB 存储每个新创建的表到表格所属于的数据库目录下它自己的文件tbl_name.ibd 里。这类似于 MyISAM 存储引擎所做的,但 MyISAM 把表分成数据文件tbl_name.MYD 和索引文件tbl_name.MYI。对于 InnoDB,数据和所以被一起存到。ibd 文件。tbl_name.frm 文件照旧依然被创建。

如果你从 my.cnf 文件删除 innodb_file_per_table 行,并重启服务器,InnoDB 在共享的表空间文件里再次创建表。

innodb_file_per_table 只影响表的创建。如果你用这个选项启动服务器,新表被用。ibd 文件来创建,但是你仍旧能访问在共享表空间里的表。如果你删掉这个选项,新表在共享表空间内创建,但你仍旧可以访问任何用多表空间创建的表。

InnoDB 总是需要共享标空间。.ibd 文件对 InnoDB 不足以去运行,共享表空间包含熟悉的 ibdata 文件,InnoDB 把内部数据词典和未作日志放在这个文件中。

注释:你不能 像对 MyISAM 一样,在数据目录之间随意地移动。ibd 文件。这是因为表定义被存在 InnoDB 共享表空间内,而且 InnoDB 必须保持事务 ID 和日志顺序号的一致性。

在一个给定的 MySQL 安装里,你可以用 RENAME TABLE 语句把。ibd 文件和关联的表从一个数据库移到另一个数据库:

RENAME TABLE old_db_name.tbl_name TO new_db_name.tbl_name;

如果你有.ibd 文件的一个干净的备份,你可以按如下操作从被起源的地方恢复它到 MySQL 安装中:

1.    发出这个 ALTER TABLE 语句:

2.      ALTER TABLE tbl_name DISCARD TABLESPACE;

警告这个语句删除当前。ibd 文件。

3.    把备份的.ibd 文件放回到恰当的数据库目录。

4.   发出这个 ALTER TABLE 语句:

5.      ALTER TABLE tbl_name IMPORT TABLESPACE;

在上下文中,一个。ibd 文件干净的备份意为:

·         .ibd 文件里没有尚未提交的事务做的修改。

·         .ibd 文件里无未合并的插入混充条目。

·         净化已经从。ibd 文件移除所有已标注删除的索引记录。

·         mysqld已经把。ibd 文件的所有已修改页面从缓冲池 刷新到文件。

你可以用下列方法生成一个。ibd 文件的干净备份:

1.    停止所有来自mysqld服务器的活动,并提交所有事务。

2.    等待直至 SHOW INNODB STATUS 显示在数据库被已经没有激活的事务,并且 InnoDB 主线程的状态是 Waiting for server activity。然后你就可以复制。ibd 文件了。

生成一个。ibd 文件的干净复制的另一个方法是使用商业的InnoDB 热备份工具

1.    使用InnoDB 热备份工具备份 InnoDB 安装。

2.    在备份上启动第二个mysqld 服务器,让它清洁备份里的。ibd 文件。

15.2.7. ;添加和删除InnoDB数据和日志文件

这一节描述在 InnoDB 表空间耗尽空间之时,或者你想要改变日志文件大小之时,你可以做的一些事情。

最简单的,增加 InnoDB 表空间大小的方法是从开始配置它为自动扩展的。为表空间定义里的最后一个数据文件指定 autoextend 属性。然后在文件耗尽空间之时,InnoDB 以 8MB 为 增量自动增加该文件的大小。增加的大小可以通过设置 innodb_autoextend_increment 值来配置,这个值以 MB 为单位,默认的是 8。

作为替代,你可以通过添加另一个数据文件来增加表空间的尺寸。要这么做的话,你必须停止 MySQL 服务器,编辑 my.cnf 文件 ,添加一个新数据文件到 innodb_data_file_path 的末尾,然后再次启动服务器。

如果最后一个数据文件是用关键字 autoextend 定义的,编辑 my.cnf 文件的步骤必须考虑最后一个数据文件已经增长到多大。获取数据文件的尺寸,把它四舍五入到最接近乘积 1024 × 1024bytes (= 1MB),然后在 innodb_data_file_path 中明确指定大致的尺寸。然后你可以添加另一个数据文件。记得只有 innodb_data_file_path 里最后一个数据可以被指定为自动扩展。

作为一个例子。假设表空间正好有一个自动扩展文件 ibdata1:

innodb_data_home_dir =
innodb_data_file_path = /ibdata/ibdata1:10M:autoextend

假设这个数据文件过一段时间已经长到 988MB。下面是添加另一个总扩展数据文件之后的配置行:

innodb_data_home_dir =
innodb_data_file_path = /ibdata/ibdata1:988M;/disk2/ibdata2:50M:autoextend

当你添加一个新文件到表空间的之后,请确信它并不存在。当你重启服务器之时,InnoDB 创建并初始化这个文件。

当前,你不能从表空间删除一个数据文件。要增加表空间的大小,使用如下步骤:

1.    使用mysqldump转储所有 InnoDB 表。

2.    停止服务器。

3.    删除所有已存在的表空间文件。

4.    配置新表空间。

5.    重启服务器。

6.    导入转储文件。

如果你想要改变你的 InnoDB 日志文件的数量和大小,你必须要停止 MySQL 服务器,并确信它被无错误地关闭。随后复制旧日志文件到一个安全的地方以防万一某样东西在关闭时出错而你需要用它们来恢复表空间。从日志文件目录删除所有旧日志文件,编辑 my.cnf 来改变日志文件配置,并再次启动 MySQL 服务器。mysqld在启动之时发现没有日志文件,然后告诉你它正在创建一个新的日志文件。

15.2.8备份和恢复InnoDB数据库

15.2.8.1. 强制恢复

15.2.8.2. 检查点

安全数据库管理的关键是定期做备份。

InnoDB 热备份工具是一个在线备份工具,你可以用它来在 InnoDB 数据库运行之时备份你的 InnoDB 数据库。InnoDB 热备份工具不要求你关闭数据库,并且它不设置任何锁定或干扰你 正常的数据库处理。InnoDB 热备份工具是非免费 (商业的) 附加软件,它每年的证书费用是每台 MySQL 服务器运行的计算机€390。请参阅InnoDB热备份主页以获得更详细的信息以及屏幕截图。

如果你可以关闭你的 MySQL 服务器,你可以生成一个包含 InnoDB 用来管理它的表的所有文件的二进制备份。使用如下步骤:

1.    关闭 MySQL 服务器,确信它是无错误关闭。

2.  复制你所有数据文件 (ibdata 文件和。ibd 文件) 到一个安全的地方。 

3.   复制你所有 ib_logfile 文件到一个安全的地方。

4.    复制 my.cnf 配置文件或文件到一个安全的地方。

5.    为你 InnoDB 表复制。frm 文件到一个安全的地方。

复制对 InnoDB 表起作用,所以你可以使用 MySQL 复制能力来在需要高可用性的数据库站点保有一份数据库的复制。

除了刚才描述的二进制备份,你也应该周期性地用mysqldump转储你的数据库。这么做的原因是,二进制文件可能被破坏而你没有注意到。转储的文件被存储成为人可读的文本文件,所以定点表的损坏 修复变得更容易。再者,因为形式更简单,严重数据损坏的机会就更小。mysqldump 也有一个--single-transaction 选项,你可以用它来做一个一致的快照而不用把其它客户端排除在外面。

要能够从上述的二进制备份恢复 InnoDB 数据库到现在,你必须让二进制日志功能打开正在运行的 MySQL 服务器。 然后你可以应用二进制日志到备份数据库以实现 point-in-time 恢复:

mysqlbinlog yourhostname-bin.123 | mysql

要从 MySQL 服务器的崩溃恢复,唯一需要的是重新启动它。InnoDB 自动检查日志并执行到现在的数据库前滚。InnoDB 自动回滚在崩溃时 呈现的未提交的事务。在恢复过程中,mysqld显示如下一些输出:

InnoDB: Database was not shut down normally.
InnoDB: Starting recovery from log files...
InnoDB: Starting log scan based on checkpoint at
InnoDB: log sequence number 0 13674004
InnoDB: Doing recovery: scanned up to log sequence number 0 13739520
InnoDB: Doing recovery: scanned up to log sequence number 0 13805056
InnoDB: Doing recovery: scanned up to log sequence number 0 13870592
InnoDB: Doing recovery: scanned up to log sequence number 0 13936128
...
InnoDB: Doing recovery: scanned up to log sequence number 0 20555264
InnoDB: Doing recovery: scanned up to log sequence number 0 20620800
InnoDB: Doing recovery: scanned up to log sequence number 0 20664692
InnoDB: 1 uncommitted transaction(s) which must be rolled back
InnoDB: Starting rollback of uncommitted transactions
InnoDB: Rolling back trx no 16745
InnoDB: Rolling back of trx no 16745 completed
InnoDB: Rollback of uncommitted transactions completed
InnoDB: Starting an apply batch of log records to the database...
InnoDB: Apply batch completed
InnoDB: Started
mysqld: ready for connections

如果数据库被损坏或磁盘出错,你必须从备份做恢复。在损坏的情况下,你首先应该找出一个没有被损坏的备份。恢复数据库备份之后,从二进制日志文件恢复。

在一些数据库损坏的情况下,仅仅转储,移除并重建一个或数个被破坏的表是足够的。你可以用 CHECK TABLE SQL 语句来检查表是否损坏,虽然 CHECK TABLE 正常地不检查每种可能的损坏,你可以使用 innodb_tablespace_monitor 来检查表空间文件内文件空间管理的完整性。

在一些情况下,明显地数据库损坏是因为操作系统损坏它自己的文件缓存,磁盘上的数据可能完好,最好是首先重启计算机。它可以消除那些显得是数据库页损坏的错误。

15.2.8.1. ;强制恢复

如果数据库页被破坏,你可能想要用 SELECT INTO OUTFILE 从从数据库转储你的表,通常以这种方法获取的大多数数据是完好的。即使这样,损坏可能导致 SELECT * FROM tbl_name或者 InnoDB 后台操作崩溃或断言,或者甚至使得 InnoDB 前滚恢复崩溃。 尽管如此,你可以用它来强制 InnoDB 存储引擎启动同时阻止后台操作运行,以便你能转储你的表。例如:你可以在重启服务器之前,在选项文件的 [mysqld] 节添加如下的行:

[mysqld]
innodb_force_recovery = 4

innodb_force_recovery 被允许的非零值如下。一个更大的数字包含所有更小数字的预防措施。如果你能够用一个多数是 4 的选项值来转储你的表,那么你是比较安全的,只有一些在损坏的单独页面上的数据会丢失。一个为 6 的值更夸张,因为数据库页被留在一个陈旧的状态,这个状态反过来可以引发对 B 树和其它数据库结构的更多破坏。

·         1 (SRV_FORCE_IGNORE_CORRUPT)

即使服务器检测到一个损坏的页,也让服务器运行着;试着让 SELECT * FROM tbl_name 跳过损坏的索引记录和页,这样有助于转储表。

·         2 (SRV_FORCE_NO_BACKGROUND)

阻止主线程运行,如果崩溃可能在净化操作过程中发生,这将阻止它。

·         3 (SRV_FORCE_NO_TRX_UNDO)

恢复后不运行事务回滚。

·         4 (SRV_FORCE_NO_IBUF_MERGE)

也阻止插入缓冲合并操作。如果你可能会导致一个崩溃。最好不要做这些操作,不要计算表统计表。

·         5 (SRV_FORCE_NO_UNDO_LOG_SCAN)

启动数据库之时不查看未完成日志:InnoDB 把未完成的事务视为已提交的。

·         6 (SRV_FORCE_NO_LOG_REDO)

不要在恢复连接中做日志前滚。

数据库不能另外地带着这些选项中被允许的选项来使用。作为一个安全措施,当 innodb_force_recovery 被设置 为大于 0 的值时,InnoDB 阻止用户执行 INSERT, UPDATE 或 DELETE 操作。

即使强制恢复被使用,你也可以 DROP 或 CREATE 表。如果你知道一个给定的表正在导致回滚崩溃,你可以移除它。你也可以用这个来停止由失败的大宗导入或失败的 ALTER TABLE 导致的失控 回滚。你可以杀掉mysqld进程,然后设置 innodb_force_recovery 为 3,使得数据库被 挂起而不需要回滚,然后舍弃导致失控回滚的表。

15.2.8.2. ;检查点

InnoDB 实现一种被认识为&ldquo;模糊”查点设置的检查点机制。InnoDB 以小批量从缓冲池 刷新已修改的数据库页。没必要以单个批次刷新缓冲池,单批次刷新实际操作中可能会在检查点设置进程中停止用户 SQL 语句的处理。

在崩溃恢复中,InnoDB 找寻被写进日志的检查点标签。它知道所有在该标签之前对数据库的修改被呈现在数据库的磁盘映像中。然后 InnoDB 从检查点往前扫描日志文件,对数据库应用已写入日志的修改。

InnoDB 以循环方式写日志文件。所有使得缓冲池里的数据库页与磁盘上的映像不同的已提交修改必须出现在日志文件中 ,以备万一 InnoDB 需要做一个恢复。这意味着,当 InnoDB 开始重新使用一个日志文件,它需要确认在磁盘上的数据库页映像包含已写进 InnoDB 准备重新使用的日志文件里的修改。换句话 说,InnoDB 必须创建一个检查点,这经常涉及已修改 数据库页到磁盘的刷新。

前面的叙述解释了为什么使你的日志文件非常大会在设置检查点中节约磁盘 I/O。设置日志文件总的大小和缓冲池一样大或者甚至比缓冲池大通常是有意义的。大日志文件的缺点是崩溃恢复要花更长的时间,因为有更多写入日志的信息要应用到数据库上。

15.2.9. ;把一个InnoDB数据库移到另一台机器

在 Windows 上, InnoDB 总是在内部以小写名字的方式存储数据库和表。要从 Unix 把二进制格式的数据库移到 Windows,或者从 Windows 移到 Unix,你应该让所有表和数据库的名字小写。要实现这个,一个方便的方式是在创建任何数据库 和表之前,在你的 my.cnf 或 my.ini 文件的 [mysqld] 节内添加如下行:

[mysqld]
lower_case_table_names=1

类似于 MyISAM 数据文件,InnoDB 数据和日志文件在所有有相同浮点数格式的平台上是二进制兼容的。你可以拷贝所有列在15.2.8 节,“InnoDB 数据库的备份和恢复”里的相关文件来简单地移动一个 InnoDB 数据库。如果浮点格式不同,但你没有在表中使用 FLOAT 或 DOUBLE 数据类型,则过程是一样:简单地拷贝相关文件。如果格式不容,且你的表包含浮点数据,你必须使用mysqldump在一台机器转储你的表,然后在另一台机器导入转储文件。

假设表空间有足够的空间供导入事务产生的大型回滚片断使用,则提高性能的一个方法是在导入数据时关掉 autocommit 模式。仅在导入整个表或表的一个片断之后提交。

15.2.10InnoDB事务模型和锁定

15.2.10.1. InnoDB锁定模式

15.2.10.2. InnoDBAUTOCOMMIT

15.2.10.3. InnoDBTRANSACTION ISOLATION LEVEL

15.2.10.4. 持续非锁定读

15.2.10.5. 锁定读 SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE

15.2.10.6. Next-Key 锁定:避免匪夷所思的问题

15.2.10.7. 持续读如何在InnoDB 中作用的例子

15.2.10.8. 在InnoDB 中用不同的 SQL 语句设定锁

15.2.10.9. MySQL 什么时候提交或回滚一个事务?

15.2.10.10. 死锁检测&回滚

15.2.10.11. 如何应对死锁

在 InnoDB 事务模式中,目的是把多版本数据库的最好特性与传统的二相锁定合并起来。InnoDB 以 Oracle 的风格,对行级进行锁定,并且 默认运行查询作为非锁定持续读。在 InnoDB 中锁定的表被存储得如此节省空间,以至于不需要锁定增大:典型地,数个用户被允许在数据库中锁定每一行,或者行的任何随机子集,而 InnoDB 不会耗尽内存。

15.2.10.1. InnoDB 锁定模式

InnoDB 实现标准行级锁定,在这里有两种类型的锁: locks:

·         共享的 (S) 锁允许一个事务去读一行 (tuple)。

·         独占的锁 (X) 允许一个事务更新或删除一行。

如果事务A 在 tuple t上持有独占锁定,来自不同事务B的对t上任一类型的锁的请求不被马上许可,取而代之地,事务B 不得不等待事务 t 释放在 tuple t上的锁。

如果事务 A 在 tuple t上持有一个共享的锁 (S),那么

·         来自不同的事务B对在t ;上X的锁定请求不能被马上许可。

·         来自不同的事务B对在tS的锁定请求可以被马上获准。因此AB持有t上的S锁定。

不仅如此,InnoDB 支持多间隔尺寸锁定,它允许记录锁和对整个表的锁共存。要使得多间隔尺寸级别的锁定实际化,额外类型的锁,被称为intention locks被使用。在 InnoDB 中,意图锁定是表锁定。 对于一个事务,意图锁定之后理想的是指明在该表中对一个行随后需要哪一类型的锁定 (共享还是独占)。有两种意图锁被用在 InnoDB 中 (假设事务T 在表R中要求一个已指出的类型的锁):

·         意图共享 (IS):事务T 意图给表T上单独的 tuple 设置S 锁定。

·         意图独占 (IX):事务T 意图给这些 tuple 设置X ;锁定。

意图锁协议如下:

·         在假设的事务可以获得对某假定行的S 锁定之前,它必须首先获得对包含该行的表的一个IS 或者更强的锁定。

·         在假设的事务可以获得对某假定行的X 锁定之前,它必须首先获得对包含该行的表的一个IX 锁定。

这些结果可以方便地用一个锁类型兼容矩阵来总结:

 

X

IX

S

IS

X

冲突

冲突

冲突

冲突

IX

冲突

兼容

冲突

兼容

S

冲突

冲突

兼容

兼容

IS

冲突

兼容

兼容

兼容

如果一个锁定与现在锁定兼容的话,它被授给一个委托事务。如果一个锁定与现存锁定冲突,它就不被授予一个委托事务。事务等待着直到冲突的现存锁定被释放掉。如果一个锁定请求与现存锁定相冲突,且不能被授予,因为它可能会导致死锁,一个错误产生。

因此,意图锁定不阻碍任何东西,除了完全表请求 (比如 LOCK TABLES ... WRITE)。IXIS锁定的主要目的是显示某人正锁定一行,或将要在表中锁定一行。

下列的例子演示当锁定请求可能会导致死锁之时一个错误会如何发生。例子中包括两个客户端 A 和 B。

首先客户端 A 创建一个包含一个行的表,然后开始一个事务。在这个事务内,A 通过在共享模式选择行获得对行的S 锁定:

mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
 
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
 
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
 
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;
+------+
| i    |
+------+
|    1 |
+------+
1 row in set (0.10 sec)

接着,客户端 B 开始一个事务并尝试从该表删除行:

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
 
mysql> DELETE FROM t WHERE i = 1;

删除操作要求一个X 锁定。因为这个锁定不兼容客户端 A 持有的S锁定,所以X 锁定不被 允许,所以请求进入对行及客户端阻挡的锁定请求队列。

最后,客户端 A 也试图从表中删除该行:

mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock;
try restarting transaction

因为客户端 A 需要一个X 锁定来删除该行,所以在这里发生死锁。尽管如此,锁定请求不被允许,因为客户端 B 已经有一个对X锁定的请求并且它正等待客户端 A 释放S锁定。因为客户端 B 之前对X 锁定的请求,被客户端 A 持有的S锁定也不能升级到X锁定。因此,InnoDB 对客户端 A 产生一个错误,并且释放它的锁定。在那一点上,客户端 B 的锁定请求可以被许可,并且客户端 B 从表中删除行。

15.2.10.2. InnoDB 和 AUTOCOMMIT

在 InnoDB 中,所有用户行为都在事务内发生。如果自动提交模式被允许,每个 SQL 语句在它自己上形成一个单独的事务。MySQL 总是带着允许自动提交来开始一个新连接。

如果自动提交模式被用 SET AUTOCOMMIT = 0 关闭,那么我们可以认为一个用户总是有一个事务打开着。一个 SQL COMMIT 或 ROLLBACK 语句结束当前事务并且一个新事务开始。两个语句都释放所有在当前事务中被设置的 InnoDB 锁定。一个 COMMIT 语句意味着在当前事务中做的改变被生成为永久的,并且变成其它用户可见的。一个 ROLLBACK 语句,在另一方面,撤销所有当前事务做的修改。

如果连接有被允许的自动提交,通过用明确的 START TRANSACTION 或 BEGIN 语句来开始一个事务,并用 COMMIT 或者 ROLLBACK 语句来结束它,这样用户仍旧可以执行一个多重语句事务。

15.2.10.3. InnoDB 和 TRANSACTION ISOLATION LEVEL

按照 SQL:1992 事务隔离级别,InnoDB 默认是可重复读的 (REPEATABLE READ)。MySQL/InnoDB 提供 SQL 标准所描述的所有四个事务隔离级别。你可以在命令行用--transaction-isolation 选项,或在选项文件里,为所有连接设置 默认隔离级别。例如,你可以在 my.inf 文件的 [mysqld] 节里类似如下设置该选项:globally

[mysqld]
transaction-isolation = {READ-UNCOMMITTED | READ-COMMITTED
                         | REPEATABLE-READ | SERIALIZABLE}

用户可以用 SET TRANSACTION 语句改变单个会话或者所有新进连接的隔离级别。它的语法如下:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
                       {READ UNCOMMITTED | READ COMMITTED
                        | REPEATABLE READ | SERIALIZABLE}

注意,对--transaction-isolation 选项的级别名中有连字符,但在对 SET TRANSACTION 语句的级别名中没有。

默认的行为是为下一个 (未开始) 事务设置隔离级别。如果你使用 GLOBAL 关键字,语句在全局对从那点开始创建的所有新连接 (除了不存在的连接) 设置 默认事务级别。你需要 SUPER 全县来做这个。使用 SESSION 关键字集为将来在当前连接上执行的事务设置默认事务级别 。

任何客户端都能自由改变会话隔离级别 (甚至在事务的中间),或者为下一个事务设置隔离级别。

你可以用下列语句查询全局和会话事务隔离级别:

SELECT @@global.tx_isolation;
SELECT @@tx_isolation;

在行级锁定中,InnoDB 使用 next-key 锁定。这意味着除了索引记录,InnoDB 也可以把索引记录前的间隙锁定到其它用户所做的紧接该索引记录之前的块插入上。一个 next-key 锁定指向一个锁定住一个索引记录和它之前的间隙的锁定。一个间隙锁定指仅锁住一些索引记录之前的间隙的锁定。

InnoDB 中每个隔离级别的详细描述如下:

·         READ UNCOMMITTED

SELECT 语句以非锁定方式被执行,但是一个可能更早期版本的记录会被用到。因此,使用这个隔离级别,比如,读是不连贯的。着也被称为&ldquo;脏读”(dirty read)。另外,这个隔离级别象READ COMMITTED 一样作用。

·         READ COMMITTED

一个有些象 Oracle 的隔离级别。所有 SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MOD 语句仅锁定索引记录,而不锁定记录前的间隙,因而允许随意紧挨着已锁定的记录插入新记录。UPDATE 和 DELETE 语句使用一个带唯一搜索条件的唯一的索引仅锁定找到的索引记录,而不包括记录前的间隙。在范围类型 UPDATE 和 DELETE 语句,InnoDB 必须对范围覆盖的间隙设置 next-key 锁定或间隙锁定以及其它用户做的块插入。这是很必要的,因为要让 MySQL 复制和恢复起作用,&ldquo;幽灵行&rdquo;必须被阻止掉。

持续读行为如同在 Oracle 中:即使在同一事务内, 每个持续读设置并读取它自己的新快照。请参阅15.2.10.4 节,“持续非锁定读”

·         REPEATABLE READ

这是 InnoDB 的默认隔离级别。带唯一搜索条件使用唯一索引的 SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE 和 DELETE 语句只锁定找到的索引记录,而不锁定记录前的间隙。用其它搜索条件,这些操作采用 next-key 锁定,用 next-key 锁定或者间隙锁定锁住搜索的索引范围,并且阻止其它用户的新插入。

在持续读中,有一个与之前隔离级别重要的差别:在这个级别,在同一事务内所有持续读读取由第一次读所确定的同一快照。这个惯例意味着如果你在同一事务内发出数个无格式 SELECT 语句,这些 SELECT 语句对相互之间也是持续的,请参阅15.2.10.4 节,“持续非锁定读”

·         SERIALIZABLE

这个级别类似 REPEATABLE READ,但是所有无格式 SELECT 语句被 隐式转换成 SELECT ... LOCK IN SHARE MODE。

15.2.10.4. 持续非锁定读

持续读意味着 InnoDB 使用它的多版本化来给一个查询展示某个时间点处数据库的快照。查询看到在那个时间点之前被提交的那些确切事务做的更改,并且没有其后的事务或未提交事务做的改变。这个规则的例外是,查询看到发布该查询的事务本身所做的改变。

如果你运行在默认的 REPEATABLE READ 隔离级别,则在同一事务内的所有持续读读取由该事务中第一个这样的读所确立的快照。你可以通过提交当前事务并在发布新查询的事务之后,为你的查询获得一个更新鲜的快照。

持续读是默认模式,在其中 InnoDBzai 在 READ COMMITTED 和 REPEATABLE READ 隔离级别处理 SELECT 语句。持续读不在任何它访问的表上设置锁定,因此,其它用户可自由地在持续读在一个表上执行的同一时间修改这些表。

注意,持续读不在 DROP TABLE 和 ALTER TABLE 上作用。持续读不在 DROP TABLE 上作用,因为 MySQL 不能使用已经被移除的表,并且 InnoDB 破坏了该表。持续读不在 ALTER TABLE 上作用,因为它在某事务内执行,该事务创建一个新表,并且从旧表往新表中插入行。现在,当你重新发出持续读之时,它不能在新表中看见任何行,因为它们被插入到一个在持续读读取的快照中不可见的事务 里。

15.2.10.5. 锁定读 SELECT ... FOR UPDATE 和 SELECT ... LOCK IN SHARE MODE

在一些环境中,一个持续读是不方便的。比如,你可能想要往表的子表里添加一个新行,并确信该子表在父表中有一个根。下列例子显示如何在你应用程序代码中实现参考的完整性。

假设你使用一个持续读去读取父表并且看到表中子表的根。不能安全地往子表添加子行吗?不,因为可能同时发生一些其它用户从父表删除父行,而你没有注意到它的情况。

解决办法是在使用 LOCK IN SHARE MODE 的锁定模式执行 SELECT:

SELECT * FROM parent WHERE NAME = 'Jones' LOCK IN SHARE MODE;

在共享模式执行一个读意味着我们读最新的可用数据,并在我们读的行设置一个共享锁定。共享模式锁防止其它人更新或删除我们已读的行。同时,如果最新的数据属于其它客户端尚未提交的事务,我们等着知道那个事务被提交。我们看到前述的查询返回父'Jones',我们可以安全地 往子表添加子记录并提交我们的事务。

让我们看另外一个例子:我们在表 child_codes 中有一个整数计数器域,我们用该表给每个添加到子表里的子项指派一个唯一的识别符。显然,使用持续读或者共享模式读去读取当前计数器的值并是一个好主意, 因为数据库的两个用户可能看到计数器的同一个值,如果两个用户试着用同一识别符往该表添加子项,就会发生一个重复键 (duplicate-key) 错误。

在这里,如果两个用户同时读计数器,当试图更新计数器之时,至少它们中有一个会发生死锁错误并终止,因此 LOCK IN SHARE MODE 并不是一个好的解决方法。

在这种情况下,有两个好方法去实现读计数器和增长计数器值:(1) ;先更新计数器,让计数器值增 1,之后读计数器,或者 (2) 用锁定模式 FOR UPDATE 先读计数器,之后计数器值增加。后一个途径可被如下实现:

SELECT counter_field FROM child_codes FOR UPDATE;
UPDATE child_codes SET counter_field = counter_field + 1;

SELECT ... FOR UPDATE 读最新的可见数据,在每个它读取的行设置独占锁定。因此,它设置与搜索的 SQL UPDATE 可能会在行上设置的锁定同样的锁定。

请注意,以上仅是一个 SELECT ... FOR UPDATE 如何起作用的例子。在 MySQL 中,事实上生成一个唯一识别符的特殊任务可被用对该表的单独访问来完成:

UPDATE child_codes SET counter_field = LAST_INSERT_ID(counter_field + 1);
SELECT LAST_INSERT_ID();

SELECT 语句仅仅取回识别符信息 (专门对当前连接)。它不访问任何表。

15.2.10.6. Next-Key 锁定:避免幽灵问题

在行级锁定中,InnoDB 使用一个名为next-key locking的算法。InnoDB 以这样一种方式执行行级锁定:当它搜索或扫描表的索引之时,它对遇到的索引记录设置共享或独占锁定。因此,行级锁定事实上是索引记录锁定。

InnoDB 对索引记录设置的锁定也映像索引记录之前的&ldquo;间隙”。如果一个用户对一个索引上的记录 R 有共享或独占的锁定,另一个用户 不能紧接在 R 之前以索引的顺序插入一个新索引记录。这个间隙的锁定被执行来防止所谓的&ldquo;幽灵问题”。假设你想要从有一个标识符值大于 100 的子表读并锁定所有子记录,并想着随后在选定行中更新一些 列:

SELECT * FROM child WHERE id > 100 FOR UPDATE;

假设在 id 列有一个索引。查询从 id 大于 100 的第一个记录开始扫描。如果设置在索引记录上的锁定不把在间隙生成的插入排除在外,一个新行可能与此同时被插进表中。如果你在同一事务内执行同样的 SELECT,你可能会在该查询返回的结果包里看到一个新行。这与事务的隔离原则是相反的:一个事务应该能够运行,以便它已经读的数据在事务过程中不改变。如果我们把一套行视为数据项,新的&ldquo;幽灵&rdquo;子记录可能会违反这一隔离原则。

当 InnoDB 扫描一个索引之时,它也锁定所以记录中最后一个记录之后的间隙。刚在前一个例子中发生:InnoDB 设置的锁定防止任何插入到 id 可能大过 100 的表。

你可以用 next-key 锁定在你的应用程序上实现一个唯一性检查:如果你以共享模式读数据, 并且没有看到你将要插入的行的重复,则你可以安全地插入你的行,并且知道在读过程中对你的行的继承者设置的 next-key 锁定与此同时阻止任何人对你的 行插入一个重复。因此,the next-key 锁定允许你锁住在你的表中并不存在的一些东西。

15.2.10.7. ;持续读如何在 InnoDB 中工作的例子

假设你正运行在默认的 REPEATABLE READ 隔离级别。当你发出一个持续读之时,即一个普通的 SELECT 语句,InnoDB 按照你的查询看到的数据库,给你的事务一个时间点。如果另一个事务在你的时间点被指定之后删除一行并提交,你不会看见已被删除的行。插入和更新被相似地处理。

你可以通过提交你的事务来提前你的时间点,然后做另一个 SELECT。

这被称为多版本并发控制 (multi-versioned concurrency control)

               User A                 User B
 
           SET AUTOCOMMIT=0;      SET AUTOCOMMIT=0;
time
|          SELECT * FROM t;
|          empty set
|                                 INSERT INTO t VALUES (1, 2);
|
v          SELECT * FROM t;
           empty set
                                  COMMIT;
 
           SELECT * FROM t;
           empty set
 
           COMMIT;
 
           SELECT * FROM t;
           ---------------------
           |    1    |    2    |
           ---------------------
           1 row in set

在这个例子中,只有当用户 B 已经提交插入,并且用户 A 也已经提交之时,用户 A 可看见被用户 B 插入的行,因此时间点 是在用户 B 提交之前。

如果你想看数据库的最新状态,你应该用 READ COMMITTED 隔离级别或用一个锁定读:

SELECT * FROM t LOCK IN SHARE MODE;

15.2.10.8. ;在 InnoDB 中不同 SQL 语句设置的锁定

在 SQL 查询处理中,一个锁定读,一个 UPDATE 或一个 DELETE 一般地对被扫描的每个索引记录设置记录锁定。如果在某查询中有一个 WHERE 条件是没什么关系的,而这个查询可能从查询的结果包中排除行。InnoDB 不记得确切的 WHERE 条件,但是仅知道哪个索引范围被扫描。记录锁定是正常的 next-key 锁定,它也阻止对紧接着记录之前的间隙的插入。

如果锁定被设置为独占,则 InnoDB 总是取回集束的索引目录并对其设置锁定。

如果你没有适合查询的索引,MySQL 不得不扫描整个表来处理查询,表的每行变成被锁定的,这样反过来阻止其它用户的所有对表的插入。创建一个好的索引让你的查询不必要扫描很多行是很重要的。

·         SELECT ... FROM是一个持续读,读取数据库的快照并且设置不锁定,除非事务隔离级别被设为 SERIALIZABLE。对于 SERIALIZABLE 级别,这个设置对它遇到的索引记录设置共享的 next-key 锁定。

·         SELECT ... FROM ... LOCK IN SHARE MODE对读遇到的所有索引记录设置共享的 next-key 锁定。

·         SELECT ... FROM ... FOR UPDATE对读遇到的所有索引记录设置独占的 next-key 锁定。

·         INSERT INTO ... VALUES (...)对被插入的行设置独占锁定。注意,这不是一个 next-key 锁定,并且不阻止其它用户在已插入行之前的间隙插入。如果发生重复键错误,对重复的索引记录设置共享锁定。

·         在一个表上初始化之前指定的 AUTO_INCREMENT 列之时,InnoDB 在与 AUTO_INCREMENT 列相关联的索引的末尾设置独占锁定。在访问自动增长计数器中,InnoDB 使用专用的表锁定模式 AUTO-INC,其中锁定仅持续到当前 SQL 语句的结束,而不是到整个事务的结束。 请参阅15.2.10.2 节,“InnoDB 和 AUTOCOMMIT”

InnoDB 取回先前初始化的 AUTO_INCREMENT 列的值而不设定任何锁定。

·         INSERT INTO T SELECT ... FROM S WHERE ... ;对每个插入到 T 的行设置独占 (非 next-key) 锁定。它在 S 上把搜索当作一个持续读,但是如果 MySQL 二进制日志功能被打开,它就对 S 设置一个共享的 next-key 锁定。InnoDB 在后一种情况不得不设置锁定:在从一个备份的前滚恢复中,每个 SQL 语句不得不以与它最初被执行的方式完全同样的方式执行。

·         CREATE TABLE ... SELECT ... ;把 SELECT 当作一个持续读来执行,或者带着共享锁定来执行,如前面的条目所述。

·         如果唯一键没有冲突,REPLACE 象一个插入一样被做。另外,对必须更新的行设置一个独占的next-key 锁定。

·         UPDATE ... WHERE ... 对搜索遇到的每个记录设置一个独占的 next-key 锁定。

·         DELETE FROM ... WHERE ... 对搜索遇到的每个记录设置一个独占的 next-key 锁定。

·         如果对一个表定义 FOREIGN KEY 约束,任何需要检查约束条件的插入,更新或删除对它看着检查约束的记录设置共享行级锁定。InnoDB 在约束失败的情况下也设置这些锁定。

·         LOCK TABLES 设置表锁定,但是是InnoDB 层之上更高的 MySQL 层设置这些锁定。如果 innodb_table_locks=1 并且 and AUTOCOMMIT=0,InnoDB 意 识到表锁定,并且 InnoDB 之上的 MySQL 层知道行级锁定。另外,InooDB 的自动死锁检测不能检测在这个表锁定被涉及之处的死锁。 同样,既然更高的 MySQL 层不知道行级锁定,很可能对另一个用户当前对其有行锁定的表获得一个表锁定。尽管如此,这并不破坏事务的完整性,如15.2.10.10 节,“死锁检测和回滚”中讨论的一样。请参阅15.2.16 节,“对 InnoDB 表的限制”

15.2.10.9.  MySQL 何时 隐式提交或回滚一个事务?

MySQL 以默认允许 autocommit 模式来开始每一个客户端连接。当 autocommit 被允许之时,如果 SQL 语句不返回错误的话,MySQL 在每个 SQL 语句之后,做一个提交。

如果你关闭 autocommit 模式并且关闭一个连接而不调用你的事务的明确提交,则 MySQL 回滚你的事务。

如果 SQL 语句返回一个错误,提交/回滚行为取决于这个错误。请参阅15.2.15 节,“InnoDB 错误处理”

下列每一个语句 (以及它们的任何同义词) 隐式结束一个事务,就好像在执行这个语句之前你做了一个 COMMIT:

·         ALTER FUNCTION, ALTER PROCEDURE, ALTER TABLE, BEGIN, CREATE DATABASE, CREATE FUNCTION, CREATE INDEX, CREATE PROCEDURE, CREATE TABLE, DROP DATABASE, DROP FUNCTION, DROP INDEX, DROP PROCEDURE, DROP TABLE, LOAD MASTER DATA, LOCK TABLES, RENAME TABLE, SET AUTOCOMMIT=1, START TRANSACTION, TRUNCATE, UNLOCK TABLES.

·         UNLOCK TABLES 仅在如果任何表当前被锁定之时,提交一个事务。

·         在 InnoDB 中的 CREATE TABLE 语句被作为一个单独的事务来处理。这意味着来自用户的 ROLLBACK 不撤销用户在事务过程中生成的 CREATE TABLE 语句。

事务不能被嵌套。当你发出 START TRANSACTION 语句或与之同义的语句之时,这是对任何当前事务 隐式提交的一个结果。

15.2.10.10. 死锁检测和回滚

InnoDB 自动检测事务的死锁,并回滚一个或几个事务来防止死锁。InnoDB 试着挑选小事务来回滚,事务的大小通过被插入、更新或删除的行的数量来确定。

如果 innodb_table_locks=1 (1 是默认值),InnoDB 意识到表锁定,其上的 MySQL 层知道 row-level 锁定。另外 InnoDB 不能在 MySQL LOCK TABLES 设定表锁定的地方或者涉及 InnoDB 之外的存储引擎设置锁定的地方检测死锁。你必须通过设定 innodb_lock_wait_timeout 系统变量的值来解决这些情况。

当 InnoD 执行完全的事务回滚之时,该事务的所有锁定被释放。尽管如此,如果单个 SQL 语句被因为错误的原因被 回滚,该 SQL 语句设定的部分锁定可能被保留。这是因为 InnoDB 以一种方式存储行锁定,在这种方式中它不能知道随后的哪个锁定是被哪个 SQL 语句设定的。

15.2.10.11. 如何处理死锁

死锁是事务型数据库典型的问题,但是除非它们频繁出现以至于你更本不能运行某个事务,它们一般是不危险的。正常地,你必须编写你的应用程序使得它们总是准备如果因为死锁而 回滚一个事务就重新发出一个事务。

InnoDB 使用自动行级锁定。即使在只插入或删除单个行的事务的情况下,你可以遇到死锁。这是因为这些操作不是真正的&ldquo;极小的”,它们自动对插入或删除的行的 (可能是数个) 索引记录设置锁定。

你可以用下列技术对付死锁减少它们发生的可能性:

·         用 Use SHOW INNODB STATUS 来确定最后一个死锁的原因。这样可以帮助你调节应用程序来避免死锁。

·         总是准备着重新发出事务,如果它因为死锁而失败了。死锁不危险,再试一次。

·         经常提交你的事务。小事务更少地倾向于冲突。

·         如果你正使用锁定读,(SELECT ... FOR UPDATE 或 ... LOCK IN SHARE MODE),试着用更低的隔离级别,比如 READ COMMITTED。

·         以固定的顺序访问你的表和行。则事务形成良好定义的查询并且没有死锁。

·         添加精心选定的索引到你的表。则你的查询需要扫描更少的索引记录并且因此设置更少的锁定。使用 EXPLAIN SELECT 来确定对于你的查询,MySQL 认为哪个索引是最适当的。

·         使用更少的锁定。如果你可以接受允许一个 SELECT 从一个旧的快照返回数据,不要给它添加 FOR UPDATE 或 LOCK IN SHARE MODE 子句。这里使用 READ COMMITTED 隔离级别是比较好的,因为每个在同一事务里的持续读从它自己新鲜的快照里读取。

·         如果没有别的有帮助的了,用表级锁定系列化你的事务。用 LOCK TABLES 对事务型表 (如 InnoDB) 的正确方法是设置 AUTOCOMMIT = 0 并且不调用 UNLOCK TABLES 直到你明确地提交了事务。例如,如果你需要写表 t1 并从表 t 读,你可以按如下做:

·                SET AUTOCOMMIT=0;
·                LOCK TABLES t1 WRITE, t2 READ, ...;
·                [do something with tables t1 and t2 here];
·                COMMIT;
·                UNLOCK TABLES;

表级锁定使得你的事务很好地排队,并且死锁被避免了。

·         领一个系列化事务的方法是创建一个辅助的“semaphore” 表,它只包含一个单行。让每个事务在访问其它表之前更新那个行。以这种方式,所有事务以序列的方式发生。注意,InnoDB 即时死锁检测算法也能在这种情况下起租用,因为系列化锁定是行级锁定。超时方法,用 MySQL 表级锁定,必须被用来解决死锁。

·         在应用程序中使用 LOCK TABLES 命令,如果 AUTOCOMMIT=1,MySQL 不设定 InnoDB 表锁定。

15.2.11InnoDB性能调节提示

15.2.11.1. SHOW INNODB STATUS 和 InnoDB Monitors

·         如果 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

15.2.11.1. 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 内部数据词典的内容。

InnoDB Monitor输出的一个例子:

mysql> SHOW INNODB STATUS\G
*************************** 1. row ***************************
Status:
=====================================
030709 13:00:59 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 18 seconds
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 413452, signal count 378357
--Thread 32782 has waited at btr0sea.c line 1477 for 0.00 seconds the semaphore:
X-lock on RW-latch at 41a28668 created in file btr0sea.c line 135
a writer (thread id 32782) has reserved it in mode wait exclusive
number of readers 1, waiters flag 1
Last time read locked in file btr0sea.c line 731
Last time write locked in file btr0sea.c line 1347
Mutex spin waits 0, rounds 0, OS waits 0
RW-shared spins 108462, OS waits 37964; RW-excl spins 681824, OS waits 375485
------------------------
LATEST FOREIGN KEY ERROR
------------------------
030709 13:00:59 Transaction:
TRANSACTION 0 290328284, ACTIVE 0 sec, process no 3195, OS thread id 34831 inser
ting
15 lock struct(s), heap size 2496, undo log entries 9
MySQL thread id 25, query id 4668733 localhost heikki update
insert into ibtest11a (D, B, C) values (5, 'khDk' ,'khDk')
Foreign key constraint fails for table test/ibtest11a:
,
  CONSTRAINT ` 0_219242 ` FOREIGN KEY (` A `, ` D `) REFERENCES ` ibtest11b ` (` A `, ` D `)
 ON DELETE CASCADE ON UPDATE CASCADE
Trying to add in child table, in index PRIMARY tuple:
 0: len 4; hex 80000101; asc ....;; 1: len 4; hex 80000005; asc ....;; 2: len 4;
 hex 6b68446b; asc khDk;; 3: len 6; hex 0000114e0edc; asc ...N..;; 4: len 7; hex
 00000000c3e0a7; asc .......;; 5: len 4; hex 6b68446b; asc khDk;;
But in parent table test/ibtest11b, in index PRIMARY,
the closest match we can find is record:
RECORD: info bits 0 0: len 4; hex 8000015b; asc ...[;; 1: len 4; hex 80000005; a
sc ....;; 2: len 3; hex 6b6864; asc khd;; 3: len 6; hex 0000111ef3eb; asc ......
;; 4: len 7; hex 800001001e0084; asc .......;; 5: len 3; hex 6b6864; asc khd;;
------------------------
LATEST DETECTED DEADLOCK
------------------------
030709 12:59:58
*** (1) TRANSACTION:
TRANSACTION 0 290252780, ACTIVE 1 sec, process no 3185, OS thread id 30733 inser
ting
LOCK WAIT 3 lock struct(s), heap size 320, undo log entries 146
MySQL thread id 21, query id 4553379 localhost heikki update
INSERT INTO alex1 VALUES(86, 86, 794,'aA35818','bb','c79166','d4766t','e187358f'
,'g84586','h794',date_format('2001-04-03 12:54:22','%Y-%m-%d %H:%i'),7
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 48310 n bits 568 table test/alex1 index symbole
trx id 0 290252780 lock mode S waiting
Record lock, heap no 324 RECORD: info bits 0 0: len 7; hex 61613335383138; asc a
a35818;; 1:
*** (2) TRANSACTION:
TRANSACTION 0 290251546, ACTIVE 2 sec, process no 3190, OS thread id 32782 inser
ting
130 lock struct(s), heap size 11584, undo log entries 437
MySQL thread id 23, query id 4554396 localhost heikki update
REPLACE INTO alex1 VALUES(NULL, 32, NULL,'aa3572','','c3572','d6012t','', NULL,'
h396', NULL, NULL, 7.31,7.31,7.31,200)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 0 page no 48310 n bits 568 table test/alex1 index symbole
trx id 0 290251546 lock_mode X locks rec but not gap
Record lock, heap no 324 RECORD: info bits 0 0: len 7; hex 61613335383138; asc a
a35818;; 1:
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 48310 n bits 568 table test/alex1 index symbole
trx id 0 290251546 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 82 RECORD: info bits 0 0: len 7; hex 61613335373230; asc aa
35720;; 1:
*** WE ROLL BACK TRANSACTION (1)
------------
TRANSACTIONS
------------
Trx id counter 0 290328385
Purge done for trx's n:o < 0 290315608 undo n:o < 0 17
Total number of lock structs in row lock hash table 70
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0 0, not started, process no 3491, OS thread id 42002
MySQL thread id 32, query id 4668737 localhost heikki
show innodb status
---TRANSACTION 0 290328384, ACTIVE 0 sec, process no 3205, OS thread id 38929 in
serting
1 lock struct(s), heap size 320
MySQL thread id 29, query id 4668736 localhost heikki update
insert into speedc values (1519229,1, 'hgjhjgghggjgjgjgjgjggjgjgjgjgjgggjgjgjlhh
gghggggghhjhghgggggghjhghghghghghhhhghghghjhhjghjghjkghjghjghjghjfhjfh
---TRANSACTION 0 290328383, ACTIVE 0 sec, process no 3180, OS thread id 28684 co
mmitting
1 lock struct(s), heap size 320, undo log entries 1
MySQL thread id 19, query id 4668734 localhost heikki update
insert into speedcm values (1603393,1, 'hgjhjgghggjgjgjgjgjggjgjgjgjgjgggjgjgjlh
hgghggggghhjhghgggggghjhghghghghghhhhghghghjhhjghjghjkghjghjghjghjfhjf
---TRANSACTION 0 290328327, ACTIVE 0 sec, process no 3200, OS thread id 36880 st
arting index read
LOCK WAIT 2 lock struct(s), heap size 320
MySQL thread id 27, query id 4668644 localhost heikki Searching rows for update
update ibtest11a set B = 'kHdkkkk' where A = 89572
------- TRX HAS BEEN WAITING 0 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 0 page no 65556 n bits 232 table test/ibtest11a index PRIM
ARY trx id 0 290328327 lock_mode X waiting
Record lock, heap no 1 RECORD: info bits 0 0: len 9; hex 73757072656d756d00; asc
 supremum.;;
------------------
---TRANSACTION 0 290328284, ACTIVE 0 sec, process no 3195, OS thread id 34831 ro
llback of SQL statement
ROLLING BACK 14 lock struct(s), heap size 2496, undo log entries 9
MySQL thread id 25, query id 4668733 localhost heikki update
insert into ibtest11a (D, B, C) values (5, 'khDk' ,'khDk')
---TRANSACTION 0 290327208, ACTIVE 1 sec, process no 3190, OS thread id 32782
58 lock struct(s), heap size 5504, undo log entries 159
MySQL thread id 23, query id 4668732 localhost heikki update
REPLACE INTO alex1 VALUES(86, 46, 538,'aa95666','bb','c95666','d9486t','e200498f
','g86814','h538',date_format('2001-04-03 12:54:22','%Y-%m-%d %H:%i'),
---TRANSACTION 0 290323325, ACTIVE 3 sec, process no 3185, OS thread id 30733 in
serting
4 lock struct(s), heap size 1024, undo log entries 165
MySQL thread id 21, query id 4668735 localhost heikki update
INSERT INTO alex1 VALUES(NULL, 49, NULL,'aa42837','','c56319','d1719t','', NULL,
'h321', NULL, NULL, 7.31,7.31,7.31,200)
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (write thread)
Pending normal aio reads: 0, aio writes: 0,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
151671 OS file reads, 94747 OS file writes, 8750 OS fsyncs
25.44 reads/s, 18494 avg bytes/read, 17.55 writes/s, 2.33 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf for space 0: size 1, free list len 19, seg size 21,
85004 inserts, 85004 merged recs, 26669 merges
Hash table size 207619, used cells 14461, node heap has 16 buffer(s)
1877.67 hash searches/s, 5121.10 non-hash searches/s
---
LOG
---
Log sequence number 18 1212842764
Log flushed up to   18 1212665295
Last checkpoint at  18 1135877290
0 pending log writes, 0 pending chkp writes
4341 log i/o's done, 1.22 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 84966343; in additional pool allocated 1402624
Buffer pool size   3200
Free buffers       110
Database pages     3074
Modified db pages  2674
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages read 171380, created 51968, written 194688
28.72 reads/s, 20.72 creates/s, 47.55 writes/s
Buffer pool hit rate 999 / 1000
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
Main thread process no. 3004, id 7176, state: purging
Number of rows inserted 3738558, updated 127415, deleted 33707, read 755779
1586.13 inserts/s, 50.89 updates/s, 28.44 deletes/s, 107.88 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================
1 row in set (0.05 sec)

关于这个输出一些要注意的:

·         如果 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文件被创建。

15.2.12. 多版本的实施

因为 InnoDB 是多版本化的数据库,它必须保持关于表空间中旧版本行的信息。这个信息被存在名为rollback segment(在 Oracle 中模拟数据结构之后) 的数据结构中。

内部地,InnoDB 往存在数据库中的每一行中添加两个域。一个 6 字节的域说明插入或更新该行的最后一个事务的事务识别符。同时,一个删除也被内部处理为一个更新,其中行中一个特殊的位被设置来标注该行为已删除。每一行也包含一个称为滚动指针的 7 字节域。滚动指针指向一个被写到回滚片断的撤销日志记录。如果该行被更新,撤销日志记录包含在该行被更新之前重建该行的内容必需的的信息。

InnoDB 使用在回滚片断中的信息来执行在事务回滚中需要的撤销操作。它也使用这个信息来为一个持续读构建更早版本的行。

在回滚片断中的撤销日志被分为插入和更新撤销日志。插入撤销日志仅在事务回滚中需要,且只要事务一提交就可以被丢弃。更新撤销日志也被用在持续读中,而且它们仅在 当前没有被 InnoDB 分配给一个快照的事务之后被丢弃,这个快照在持续读中可能会需要更新撤销日志的信息来建立一个数据库行的早期版本。

你必须记得规律地提交你的事务,包括那些只发布持续读的事务。否则,InnoDB 不能从更新撤销日志丢弃数据,并且回滚片断可能变得太大,填满你的表空间。

在一个回滚片断里,一个撤销日志记录的物理尺寸典型地小于相应的已插入行或已更新行。你可以用这个信息来计算回滚片断需要的空间。

在 InnoDB 多版本化方案中,当你用 SQL 语句删除一行之时,该行没有被从数据库立即物理删除掉。 只有当InnoDB 可以丢弃为删除而被写的更新撤销日志记录时,InnoDB 也物理地从数据库删除相应行和它的索引记录。这个删除操作被成为精华,它运行得很快,通常与做删除的 SQL 语句花的时间在一个数量级

在某一情景下,在那里,用户以几乎相同的比率,小批次地在表中插入和删除行,净化线程开始滞后 是可能的,并且表变得越来越大,使得每样事都是磁盘绑定的而且非常慢。即使表仅载有 10MB 有用的数据,它可能变得用所有的死行占据 10GB 空间。在这种情况下,节流新操作,并分配更多的资源来净化线程可能是比较好的。启动选项和可设置全球变量 innodb_max_purge_lag 就是为这个目的而存在的。请参阅15.2.4 节,“InnoDB 启动选项” 以获得更多信息。

15.2.13. 表和索引结构

15.2.13.1. 索引的物理结构

15.2.13.2. 缓冲插入

15.2.13.3. 适应的哈希索引

15.2.13.4. 物理记录结构

MySQL 为表把它的数据词典信息以。frm 文件的形式存在数据库目录里,这对所有 MySQL 存储引擎都是真的。但是每个 InnoDB 表在表空间内的 InnoDB 内部数据词典里有它自己的条目。当 MySQL 移除表或数据库,它不得不删除。frm 文件和 InnoDB 数据词典内的相应条目。这就是为什么你不能在数据库之间简单地移动。frm 文件来移动 InnoDB 表。

每个 InnoDB 表有专门索引,被称为clustered index,对行的数据被存于其中。如果你对你的表定义一个 PRIMARY KEY, 主键的索引是集束索引。

如果你没有为表定义 PRIMARY KEY,MySQL 拾取第一个仅有 NOT NULL 列的 UNIQUE 索引作为主键,并且 InnoDB 把它当作集束索引来用。如果表中没有这样一个索引,InnoDB 内部产生一个集束索引,其中用 InnoDB 在这样一个表内指定给行的行 ID 来排序行。行 ID 是一个 6 字节的域,它在新行被插入的时候单一地增加。因此被行 ID 排序的行是物理地按照插入顺序排的。

通过集束索引访问一个行是较快的,因为行数据是在索引搜索引导的同一页面。如果表是巨大的,当对比于传统解决方案,集束索引构架经常节约磁盘 I/O。(在许多数据库,数据传统地被存在与索引记录不同的页)。

在 InnoDB 中,非集束索引里的记录 (也称为第二索引) 包含对行的主键值。InnoDB 用这个 主键值来从集束索引中搜索行。注意,如果主键是长的,第二索引使用更多空间。

InnoDB 比较 CHAR 和 VARCHAR 字符串不同长度,以便在较短字符串中剩下的长度被处理视为用空格补上的。

15.2.13.1. 索引的物理结构

所有 InnoDB 的索引是 B 数,其中索引记录被存储在树的树叶页。一个索引页的默认大小是 16KB。当新记录被插入,InnoDB 试着为将来索引记录的插入和更新留下十六分之一的空白页。

如果索引记录以连续的顺序被插入 (升序或者降序),结果索引页大约是 15/16 满。如果记录被以随机的顺序被插入,页面是从 1/2 到 15/16 满。如果索引页的填充因子降到低于 1/2,InnoDB 试着搜索索引树来释放页。

15.2.13.2. 缓冲插入

在数据库应用中,主键是一个唯一的识别符,并且新行被以主键的升序来插入,这是个常见的情况。因此,到集束索引的插入不需要从一个磁盘随机读。

另一方面,第二索引通常是非唯一的,到第二索引的插入以相对随机次序发生。这可能会导致大量的随机磁盘 I/O 操作,而没有一个被用在 InnoDB 中的专用机制。

如果一个索引记录应该被插入到一个非唯一第二索引,InnoDB 检查第二索引页是否在缓冲池中。如果是,InnoDB 直接插入到索引页。如果索引页没有在缓冲池中被发现,InnoDB 插入记录到一个专门的插入缓冲结构。插入缓冲被保持得如此小以至于它完全适合在缓冲池,并且可以非常快地做插入。

插入缓冲周期地被合并到数据库中第二索引树里。把数个插入合并到索引树的同一页,节省磁盘 I/O 操作,经常地这是有可能的。据测量,插入缓冲可以提高到表的插入速度达 15 倍。

在插入事务被提交之后,插入缓冲合并可能连续发生。实际上,服务器关闭和重启之后,这会连续发生。(请参阅15.2.8.1 节,“强制恢复”)。

当许多第二索引必须被更新之时,并且许多行已被插入之时,插入缓冲合并可能需要数个小时。在这个时间内,磁盘 I/O 将会增加,这样会导致磁盘绑定查询明显缓慢。另一个明显的后台 I/O 操作是净化线程 (请参阅15.2.12 节,“实现多版本化”)。

15.2.13.3. 适应的哈希索引

如果一个表几乎完全配合主内存,在其上执行查询最快的方法就是使用哈希索引。InnoDB 有一个自动机制,它监视对为一个表定义的索引的索引搜索。如果 InnoDB 注意到查询会从建立一个哈希索引中获益,它会自动地这么做。

注意,哈希索引总是基于表上已存在的 B 树索引来建立。根据 InnoDB 对 B 树索引观察的搜索方式,InnoDB 会在为该 B 树定义的任何长度的 键的一个前缀上建立哈希索引。 哈希索引可以是部分的:它不要求整个 B 树索引被缓存在缓冲池。InnoDB 根据需要对被经常访问的索引的那些页面建立哈希索引。

在某种意义上,InnoDB 通过针对丰富主内存的适应的哈希索引机制来剪裁自己,更加靠近主内存数据库的架构。

15.2.13.4. ;物理记录结构

InnoDB 表中的记录有如下特征:

·         InnoDB 中每个索引记录包含一个 6 字节的头。这个头被用来将连续的记录连接在一起,并且也用在 row-level 锁定中。

·         集束索引中的记录包含对所有自定义列的域。此外,有一个 6 字节的域给事务 ID 以及一个 7 字节的域给滚动指针。

·         如果没有为一个表定义初级键,每个集束索引记录也包含一个 6 字节的行 ID 域。

·         每个第二索引记录也包含为集束索引键定义的所有域。

·         一个记录也包含一个指向该记录每一个域的指针,如果在一个记录中域的总长度小于 128 字节,该指针时一个字节;否则就是 2 字节。这些指针的阵列被称为记录目录。这些指针指向的区域被称为记录的数据部分。

·         内部地,InnoDB 以固定长度格式存储固定长度字符列,比如 CHAR(10)。InnoDB 从 VARCHAR 列截短跟踪空间。注意,MySQL 可以内部地把 CHAR 列转换为 VARCHAR 列。请参阅13.1.5.1 节,“安静的列规格改变”

·         一个 SQL 的 NULL 值在记录目录里保留 1 或 2 字节。此外,SQL 的 NULL 值如果被保存在可变长度列,则在记录数据部分保留零字节。在一个固定长度列,它在记录的数据部分保留该列的固定长度。为 NULL 值保留固定空间背后的动机是之后该 列从 NULL 值到非 NULL 值的更新可以就地完成,且不会导致索引页的碎片。

15.2.14.文件空间管理和磁盘 I/O

15.2.14.1. 磁盘 I/O

15.2.14.2. 为表空间使用原始设备

15.2.14.3.文件空间管理

15.2.14.4. 整理表碎片

15.2.14.1. 磁盘 I/O

InnoDB 使用模拟异步磁盘 I/O:InnoDB 创建许多线程来处理 I/O 操作,比如 read-ahead.

在 InnoDB 中有两个read-ahead 试探:

·         在连续 read-ahead 中,如果 InnoDB 注意到在表空间中对一个片断的访问方式是连续的,它就预先布置一批 数据库页的读 给 I/O 系统。

·         在随机 read-ahead 中,如果 InnoDB 注意到表空间中的一些区域看起来进入完全读进缓冲池中的处理中,它就布置剩余的读到 I/O 系统。

InnoDB 使用一个被称为doublewrite的新颖的文件 刷新技术。它给操作体统崩溃或掉电后的恢复添加了安全,并且通过减少对 fsync() 操作的需要,它在多数 Unix 变种上改善了性能。.

Doublewrite 意为在向一个数据文件写页之前,InnoDB 首先把它们写到一个毗邻的表空间区域,称为 doublewrite 缓冲。仅在写然后 刷新到 doublewrite 已经完成之前,InnoDB 写页面到它们在表空间中恰当的位置。如果操作系统在写页面的中间崩溃,在恢复过程中,InnoDB 可以在随后从 doublewrite 缓冲中找到页面的一个良好复制。

15.2.14.2. 为表空间使用原始设备

你也可以使用原始磁盘分区作为表空间数据文件。通过使用原始磁盘,你可以在 Windows 和一些 Unix 系统上执行 non-buffered I/O 而无须文件系统开支,这样可以改善性能

当你创建一个新数据文件之时,你必须在 innodb_data_file_path 里紧接着数据文件尺寸之后放置 关键字 newraw。分区必须至少和你指定的尺寸一样大,注意,在 InnoDB 中,1MB 是 1024x1024 字节, 但是在磁盘规格中,1MB 通常意为 1,000,000 字节。

[mysqld]
innodb_data_home_dir=
innodb_data_file_path=/dev/hdd1:3Gnewraw;/dev/hdd2:2Gnewraw

下次你启动服务器之时,InnoDB 注意到关键字 newraw 并初始化新分区。但是仍然并不创建或改变任何 InnoDB 表。另外,当你重启服务器之时,InnoDB 重新初始化分区,你的改变会丢失。(从3.23.44 启动,作为一个安全措施, 当用 newraw 指定任何分区之时,InnoDB 阻止用户修改数据)。

InnoDB 初始化新分区之后,停止服务器,在对行的数据文件规格中改变 newraw:

[mysqld]
innodb_data_home_dir=
innodb_data_file_path=/dev/hdd1:5Graw;/dev/hdd2:2Graw

然后重启动服务器,并且 InnoDB 允许做改变。

在 Windows 上,你可以象这样分配磁盘分区为一个数据文件:

[mysqld]
innodb_data_home_dir=
innodb_data_file_path=//./D::10Gnewraw

对于访问物理驱动器,//./ 相当于 Windows 语法的\\.\ 。

当你使用原始磁盘分区之时,确信它们有允许被用来运行 MySQL 服务器的帐号读和写访问的许可。

15.2.14.3.文件空间管理

你在配置文件中定义的数据文件形成 InnoDB 的表空间。文件被简单地连起来以形成表空间。没有条纹在使用。当前你不能定义你的表被分配到表空间中的位置。但是,在一个新创建的表中间中,InnoDB 从第一个数据文件开始分配空间。

表空间包含数据库页,默认大小是 16KB。这些页被分组成 64 个连续页的范围。表空间内的文件在 InnoDB 中被称为片断。术语“rollback segment&rdquo;有一些混淆,因为它确切地包含许多表空间片断。

在 InnoDB 中,每个索引分配两个片断。一个是给 B 树的非树叶节点的,另一个是给树叶节点的。在这里,理想的是为包含数据的树叶节点达到更好的有序性。

当一个片断在表空间内长大,InnoDB 单独地分配最先的 32 页给它。此后 InnoDB 开始分配整个范围给该片断。InnoDB 可以一次给一个大片断添加多达 4 个范围以确保数据良好的连续性。

在表空间中的一些页包含其它页的位图,因此在一个 InnoDB 表空间中的一些范围不能被整个地分配给片断,只能作为单个页被分配。

当你发出 SHOW TABLE STATUS 询问表空间里可用的自由空间之时,InnoDB 报告在表空间中完全自由的范围。InnoDB 总是为扫除和其它内部目的保留一些范围,这些保留的范围不包括在自由空间里。

当你从一个表中删除数据之时,InnoDB 联系相应的 B 树索引。是否释放单独页或是范围到表空间取决删除的方式,因此被释放的空间变成对其它用户可用,但是记住,已删除的行仅在该行不再被事务 回滚或持续读所需要之后的一个 (自动) 净化操作中被物理删除。

15.2.14.4. 整理表碎片

如果有随机插入到表的索引或从表的索引随机删除,索引可能变成碎片的。碎片意思是索引页在磁盘上的物理排序并不接近页上记录的索引排序,或者在分配给索引的 64 页块上有许多没有被使用的页。

碎片的一个&ldquo;同义词&rdquo;是一个表占据的空间超过它应该占据的空间的大 小。确切是多少,这是很难去确定的。所有 InnoDB 数据和索引被存在 B 树中,并且它们的填充因子可能从 50% 到 100%。碎片的另一个&ldquo;同 义词&rdquo;是一个表扫描例如:

SELECT COUNT(*) FROM t WHERE a_non_indexed_column <> 12345;

花了超过它应该花的时间。(在上面的查询中我们&ldquo;欺骗”SQL 优化器来扫描集束索引,而不是一个第二索引 )。多数磁盘可以读 10MB/s 到 50MB/s,这可以被用来评估一个表扫描可以多快地运行。

如果你周期地执行“null” ALTER TABLE 操作,它就可以加速索引扫描: 

ALTER TABLE tbl_name ENGINE=INNODB

这导致 MySQL 重建表。另一个执行碎片整理操作的办法是使用mysqldump来转储一个表到一个文本文件,移除表,并重新从转储文件重装载它。

如果到一个索引的插入总是升序的,并且记录仅从末尾被删除,InnoDB 文件空间管理保证在索引中的碎片不会发生。

15.2.15. InnoDB 错误处理

15.2.15.1. InnoDB 错误代码

15.2.15.2. 操作系统错误代码

在 InnoDB 中错误处理不像 SQL 标准中指定的一样。按照标准,在 SQL 语句过程中的任何错误应该导致该语句的 回滚。InnoDB 有时仅回滚部分语句,或者整个事务。下列条目叙述 InnoDB 如何执行错误处理:

·         如果你耗尽表空间中的文件空间,你使得 MySQL 表完全错误,并且 InnoDB 返回 SQL 语句。

·         一个事务死锁导致 InnoDB 回滚整个事务,在锁定等待超时的情况,InnoDB 仅回滚最近的 SQL 语句。

当一个事务回滚因为死锁或锁定等待超时而发生,它在事务中撤销语句的作用。但是如果事务是用 START TRANSACTION 或 BEGIN 语句开始的,它就不撤销该语句。进一步,SQL 语句变成事务的一部分直到 COMMIT, ROLLBACK 或者导致暗地提交的 SQL 语句发生。

·         如果你没有在语句中指定 IGNORE 选项,重复键错误回滚 SQL 语句。

·         行太长错误回滚 SQL 语句。

·         其它错误主要被代码的 MySQL 层 (在 InnoDB 存储引擎级别以上) 探测,它们回滚相应的 SQL 语句。在单个 SQL 语句中的回滚中锁定不被释放。

在暗地回滚过程中,就像在一个明确的 ROLLBACK SQL 命令的执行过程中一样,SHOW PROCESSLIST 在 State 列为有关的连接显示 Rolling back。

15.2.15.1. InnoDB 错误代码

下面的这个不完全列表是你可能遇见的常见的 InnoDB 专有错误,带着为什么发生的原因以及如何该解决问题的相关信息

·         1005 (ER_CANT_CREATE_TABLE)

不能创建表。如果错误信息字符串指向 errno 150,因为 外键约束被不正确地形成,所以表创建失败。

·         1016 (ER_CANT_OPEN_FILE)

虽然对某表的。frm 文件存在,但不能从 InnoDB 数据文件找到该 InnoDB 表。请参阅15.2.17.1 节,“InnoDB 数据词典操作 故障诊断和排除”

·         1114 (ER_RECORD_FILE_FULL)

InnoDB 耗尽表空间中的可用空间,你应该重新配置表空间来添加一个新数据文件。

·         1205 (ER_LOCK_WAIT_TIMEOUT)

锁定等待超时过期。事务被回滚。

·         1213 (ER_LOCK_DEADLOCK)

事务死锁。你应该重运行事务。

·         1216 (ER_NO_REFERENCED_ROW)

你正试着添加一行,但没有父行,并且一个外键约束失败。你应该先添加父行。

·         1217 (ER_ROW_IS_REFERENCED)

你正试图删除一个有子行的父行,并且一个外键约束失败。你应该先删除子行。

15.2.15.2. ;操作系统错误代码

要打印一个操作系统错误号的意思,请使用 MySQL分发版里的perror程序。

下面表提供一些常用 Linux 系统错误代码。更完整的列表请参阅Linux source code

·         1 (EPERM)

操作不被允许

·         2 (ENOENT)

无此文件或目录

·         3 (ESRCH)

无此进程

·         4 (EINTR)

中断的系统调用

·         5 (EIO)

I/O 错误

·         6 (ENXIO)

无此设备或地址

·         7 (E2BIG)

Arg 列表太长

·         8 (ENOEXEC)

Exec 合适错误

·         9 (EBADF)

坏文件号

·         10 (ECHILD) 

无子进程

·         11 (EAGAIN)

再试一次

·         12 (ENOMEM)

内存耗尽

·         13 (EACCES)

许可被否定

·         14 (EFAULT)

坏地址

·         15 (ENOTBLK)

阻止需求的设备

·         16 (EBUSY)

设备或资源忙

·         17 (EEXIST)

文件存在

·         18 (EXDEV)

交叉设备连接

·         19 (ENODEV)

无此设备

·         20 (ENOTDIR)

不是一个目录

·         21 (EISDIR)

是一个目录?

·         22 (EINVAL)

非法参量

·         23 (ENFILE)

文件表溢出

·         24 (EMFILE)

打开的文件过多

·         25 (ENOTTY)

设备不适合的 ioctl

·         26 (ETXTBSY)

文本文件忙

·         27 (EFBIG)

文件太大

·         28 (ENOSPC)

设备上没空间了

·         29 (ESPIPE)

非法查找

·         30 (EROFS)

只读文件系统

·         31 (EMLINK)

太多连接

下列表提供一列常用 Windows 系统错误代码。完整列表请访问Microsoft website

·         1 (ERROR_INVALID_FUNCTION)

不正确函数。

·         2 (ERROR_FILE_NOT_FOUND)

系统不能找到指定的文件。

·         3 (ERROR_PATH_NOT_FOUND)

系统不能找到指定的路径。

·         4 (ERROR_TOO_MANY_OPEN_FILES)

系统不能打开文件。

·         5 (ERROR_ACCESS_DENIED)

访问被拒绝。

·         6 (ERROR_INVALID_HANDLE)

句柄非法。

·         7 (ERROR_ARENA_TRASHED)

存储控制块被破坏。

·         8 (ERROR_NOT_ENOUGH_MEMORY)

无足够存储来处理这个命令。

·         9 (ERROR_INVALID_BLOCK)

存储控制块地址非法。

·         10 (ERROR_BAD_ENVIRONMENT)

环境不正确。

·         11 (ERROR_BAD_FORMAT)

试图用不正确的格式装载一个程序。

·         12 (ERROR_INVALID_ACCESS)

访问代码不合法。

·         13 (ERROR_INVALID_DATA)

数据不合法。

·         14 (ERROR_OUTOFMEMORY)

无足够的存储来完成这个操作。

·         15 (ERROR_INVALID_DRIVE)

系统不能找到指定的驱动器。

·         16 (ERROR_CURRENT_DIRECTORY)

目录不能被删除。

·         17 (ERROR_NOT_SAME_DEVICE)

系统不能移动此文件到一个不同的磁盘驱动器。

·         18 (ERROR_NO_MORE_FILES)

没有更多文件。

·         19 (ERROR_WRITE_PROTECT)

媒质写保护。

·         20 (ERROR_BAD_UNIT)

系统不能找到指定的设备。

·         21 (ERROR_NOT_READY)

设备未准备好。

·         22 (ERROR_BAD_COMMAND)

设备不能识别此命令。

·         23 (ERROR_CRC)

数据错误 (循环冗余码校验).

·         24 (ERROR_BAD_LENGTH)

程序发出一个命令,但是命令长度不正确。

·         25 (ERROR_SEEK)

驱动器不能在磁盘上定位指定区域或磁道。

·         26 (ERROR_NOT_DOS_DISK)

指定的磁盘或软盘不能被访问。

·         27 (ERROR_SECTOR_NOT_FOUND)

驱动器不能找到请求的扇区。

·         28 (ERROR_OUT_OF_PAPER)

打印机缺纸。

·         29 (ERROR_WRITE_FAULT)

系统不能写指定设备。

·         30 (ERROR_READ_FAULT)

系统不能从指定设备读。

·         31 (ERROR_GEN_FAILURE)

附加到系统的设备不起作用。

·         32 (ERROR_SHARING_VIOLATION)

进程不能访问文件,因为它正被另一个进程使用。

·         33 (ERROR_LOCK_VIOLATION)

进程不能访问文件,因为另一个进程已经锁定文件的一部分。

·         34 (ERROR_WRONG_DISK)

驱动器的的磁盘不正确,请插入 %2 (卷系列号: %3) 到驱动器 %1.

·         36 (ERROR_SHARING_BUFFER_EXCEEDED)

太多文件被打开以共享。

·         38 (ERROR_HANDLE_EOF)

到达文件的末尾。

·         39 (ERROR_HANDLE_DISK_FULL)

磁盘已满。

·         87 (ERROR_INVALID_PARAMETER)

参数不正确。(如果你在 Windows 中得到这个错误,并且已经在 my.cnf 或 my.ini 文件中设置 innodb_file_per_table,则添加 innodb_flush_method=unbuffered 到你的 my.cnf 或 my.ini 文件)。

·         112 (ERROR_DISK_FULL)

磁盘已满。

·         123 (ERROR_INVALID_NAME)

文件名,目录名或者卷标语法不正确。

·         1450 (ERROR_NO_SYSTEM_RESOURCES)

存在系统资源不够完成请求的服务。

15.2.16对 InnoDB表的限制

·         一个表不能包含超过 1000 列。

·         内部最大键长度是 3500 字节,但 MySQL 自己限制这个到 1024 字节。

·         除了 VARCHAR, BLOB 和 TEXT 列,最大行长度稍微小于数据库页的一半。即,最大行长度大约 8000 字节。LONGBLOB 和 LONGTEXT 列必须小于 4GB,总的行长度,页包括 BLOB 和 TEXT 列,必须小于 4GB。InnoDB 在行中存储 VARCHAR,BLOB 或 TEXT 列的前 768 字节,余下的存储的分散的页面中。

·         虽然 InnoDB 内部地支持行尺寸大于 65535,你不能定义一个包含 VARCHAR 列的,合并尺寸大于 65535 的行。

·                mysql> CREATE TABLE t (a VARCHAR(8000), b VARCHAR(10000),
·                    -> c VARCHAR(10000), d VARCHAR(10000), e VARCHAR(10000),
·                    -> f VARCHAR(10000), g VARCHAR(10000));
·                ERROR 1118 (42000): Row size too large. The maximum row size for the
·                used table type, not counting BLOBs, is 65535. You have to change some
·                columns to TEXT or BLOBs

·         在一些更老的操作系统上,数据文件必须小于 2GB。

·         InnoDB 日志文件的合并尺寸必须小于 4GB。

·         最小的表空间尺寸是 10MB。最大的表空间尺寸是 4,000,000,000 个数据库页 (64TB)。这也是一个表的最大尺寸。

·         InnoDB 表不支持FULLTEXT 索引。

·         ANALYZE TABLE 通过对每个索引树做八次随机深入并相应地更新索引集估值,这样来计数集。注意,因为这是仅有的估值,反复运行 ANALYZE TABLE 会产生不同数。这使得 ANALYZE TABLE 在 InnoDB 表上很快,不是百分百准确,因为它没有考虑所有的行。

MySQL 不仅在汇合优化中使用索引集估值。如果一些汇合没有以正确的方式优化,你可以试一下 ANALYZE TABLE 。很少有情况,ANALYZE TABLE 没有产生对你特定的表足够好的值,你可以使用 FORCE INDEX 在你查询中来强制使用特定索引,或者设置 max_seeks_for_key 来确保 MySQL 在表扫描之上运行索引查找。请参阅5.3.3 节,“服务器系统变量”。请参阅A.6 节,“优化器相关的问题”

·         在 Windows 上,InnoDB 总是内部地用小写字母存储数据库和表名字。要把数据库以二进制形式从 Unix 移到 Windows,或者从 Windows 移到 Unix,你应该让所有数据库和表的名字都是小写。

·         警告: ;不要在 MySQL 数据库内的把 MySQL 系统表从 MyISAM 转为InnoDB表!这是一个不被支持的操作。如果你这么做了,MySQL 直到你从备份恢复旧系统表,或用mysql_install_db脚本重建系统表才重启动。

·         InnoDB 在表内不保留行的内部计数。(因为多版本化,这可能确实有些复杂 )。要处理一个 SELECT COUNT(*) FROM t 语句,InnoDB 必须扫描表的一个索引,如果这个索引不在缓冲池中,扫描需要花一些时间。要获得快速计数,你不得不使用一个自己创建的计数器表,并让你的应用按照它做的插入和删除来更新它。如果你的表格不经常改变,使用 MySQL 查询缓存时一个好的解决方案。如果大致的行数就足够了,则 SHOW TABLE STATUS 也可被使用。请参阅15.2.11 节,“InnoDB 性能调节提示”

·         对于 AUTO_INCREMENT 列,你必须总是为表定义一个索引,并且索引必须包含 AUTO_INCREMENT 列。在 MyISAM 表中,AUTO_INCREMENT 列可能时多 列索引的一部分。

·         当你重启 MySQL 服务器之时,InnoDB 可能为一个 AUTO_INCREMENT 列重使用一个旧值 (即,一个被赋给一个老的已 回滚的事务的值)。

·         当一个 AUTO_INCREMENT 列用完值,InnoDB 限制一个 BIGINT 到-9223372036854775808 以及 BIGINT UNSIGNED 到 1。尽管如此,BIGINT 值有由 64 位,所以注意到,如果你要一秒输入 100 万个行,在 BIGINT 到达它上限之前,可能还需要将近 30 万年。用所有其它整数类型 列,产生一个重复键错误。这类似于 MyISAM 如何工作的,因为它主要是一般 MySQL 行为,并不特别关于任何存储引擎。

·         DELETE FROM tbl_name不重新生成表,但取而代之地删除所有行,一个接一个地删除。

·         TRUNCATE tbl_name为 InnoDB 而被映射到 DELETE FROM tbl_name 并且不重置 AUTO_INCREMENT 计数器。

·         SHOW TABLE STATUS 不能给出关于InnoDB 表准确的统计数据,除了被表保留的物理尺寸。行计数仅是在 SQL 优化中粗略的估计。

·         在 MySQL 5.1 中,如果 innodb_table_locks=1(1 是默认值) MySQL LOCK TABLES 操作在每一个表上获取两个锁定。除了在 MySQL 层的表锁定,它也获得一个 InnoDB 表锁定。旧版的 MySQL 不获取 InnoDB 表锁定,旧行为可以通过设置 innodb_table_locks=0 来选择。如果没有 InnoDB 表锁定被获得,即使表的一些记录被其它事务锁定,LOCK TABLES 完成。 

·         所有被一个事务持有的 InnoDB 锁定在该事务被提交或中止之时被释放。因此在 AUTOCOMMIT=1 模式,在 InnoDB 表上调用是没有太多意义的,因为被需求的 InnoDB 表锁定可能会被立即释放。

·         有时,在事务的过程中锁定更多的表可能是有用的。不幸地,MySQL 中的 LOCK TABLES 执行一个暗地的 COMMIT 和 UNLOCK TABLES。LOCK TABLES 的一个 InnoDB 变量已经被计划, 该计划在事务的中间被执行。

·         为建立复制从服务器的 LOAD TABLE FROM MASTER 语句对InnoDB 表不起作用。一个工作区在主服务器上更换表为MyISAM 的,然后做负载,之后更换主服务器表回到 InnoDB 中。

·         在 InnoDB 中默认数据库页的大小是 16KB。通过编译代码,你可以在 8KB 到 64KB 之间来设置这个值。你不得不更新在 univ.i 源文件中的 UNIV_PAGE_SIZE 和 UNIV_PAGE_SIZE_SHIFT 的值。

·         在 MySQL 5.1 中,触发器不被级联的外键行为激活。

15.2.17InnoDB故障诊断和排除

15.2.17.1. InnoDB 数据词典操作的错误诊断和排除

·         一个总的规则是,当一个操作失败或这你怀疑有一个缺陷。你应该查看 MySQL 服务器的错误日志,该日志典型地有一个有些象hostname.err 这样的名字,或者在 Windows 上是 mysql.err 这样的。

·         故障诊断与排除之时,通常最好从命令提示符运行 MySQL 服务器,而不是从mysqld_safe包运行,或不作为一个 Windows 服务来运行。你可以看mysqld打印到控制台上的内容,因此更好掌握发生了什么。在 Windows 上,你必须用--console 选项启动服务器将输出定向到控制台窗口

·         使用 InnoDB Monitors 获取关于某问题的信息。如果问题是性能相关的,或者你的服务器看起来被挂起,你应该使用 innodb_monitor 来打印 InnoDB 内部状态的信息,如果问题是关于锁定,则使用 innodb_lock_monitor。如果问题是在表的创建或其它数据词典操作,使用 innodb_table_monitor 来打印 InnoDB 内部数据词典的内容。

·         如果你猜测一个表被破坏,则在该表上运行 CHECK TABLE。

15.2.17.1. InnoDB 数据词典操作错误诊断和排除

表的一个特殊问题是 MySQL 服务器以。frm 文件来保存数据词典信息,它被放在数据库目录,然而 InnoDB 也存储信息到表空间文件里它自己的数据词典里。如果你把。frm 文件移来移去 ;或者,如果服务器在数据词典操作的中间崩溃,.frm 文件可能结束与 InnoDB 内部数据词典的同步。

一个不同步的数据词典的症状是 CREATE TABLE 语句失败。如果发生这种情况,你应该查看服务器的错误日志。如果日志说表已经存在于 InnoDB 内部数据词典当中,你在 InnoDB 表空间文件内有一个孤表,它没有对应的。frm 文件。错误信息看起来象如下的:

InnoDB: Error: table test/parent already exists in InnoDB internal
InnoDB: data dictionary. Have you deleted the .frm file
InnoDB: and not used DROP TABLE? Have you used DROP DATABASE
InnoDB: for InnoDB tables in MySQL version <= 3.23.43?
InnoDB: See the Restrictions section of the InnoDB manual.
InnoDB: You can drop the orphaned table inside InnoDB by
InnoDB: creating an InnoDB table with the same name in another
InnoDB: database and moving the .frm file to the current database.
InnoDB: Then MySQL thinks the table exists, and DROP TABLE will
InnoDB: succeed.

你可以按照错误日志里给的指示移除一个孤表。如果还是不能成功地使用 DROP TABLE,问题可能是因为在mysql客户端里的名字完成。要解决这个问题,用--disable-auto-rehash 选项来启动mysql客户端并再次尝试 DROP TABLE 。(有名字完成打开着,mysql试着构建个表名字的列表,当一个正如描述的问题存在之时,这个列表就不起作用)。

不同步数据词典的另一个&ldquo;同义词&rdquo;是 MySQL 打印一个不能打开。InnoDB 文件的错误:

ERROR 1016: Can't open file: 'child2.InnoDB'. (errno: 1)

在错误日志你可以发现一个类似于此的信息:

InnoDB: Cannot find table test/child2 from the internal data dictionary
InnoDB: of InnoDB though the .frm file for the table exists. Maybe you
InnoDB: have deleted and recreated InnoDB data files but have forgotten
InnoDB: to delete the corresponding .frm files of InnoDB tables?

这意味这有一个孤单的。frm 文件,在 InnoDB 内没有相对应的表。你可以通过手动删除来移除这个孤单的。frm 文件。

如果 MySQL 在一个 ALTER TABLE 操作的中间崩溃,你可以用 InnoDB 表空间内临时孤表来结束。你可以用 innodb_table_monitor 看一个列出的表,名为#sql-...。如果你把表的名字包在`(backticks) 里,你可以在名字包含“#&rdquo;字符的表上执行 SQL 语句。因此,你可以用前述的的方法象移除其它孤表一样移除这样一个孤表。注意,要在 Unix 外壳里复制或重命名一个文件,如果文件名包含"#"字符,你需要把文件名放在双引号里。

15.3. MERGE存储引擎

15.3.1. MERGE表的问题

MERGE 存储引擎,也被认识为 MRG_MyISAM 引擎,是一个相同的可以被当作一个来用的 MyISAM 表的集合。&ldquo;相同&rdquo;意味着所有表同样的 列和索引信息。你不能合并列被以不同顺序列于其中的表,没有恰好同样列的表,或有不同顺序索引的表。而且,任何或者所有的表可以用myisampack来压缩。请参阅8.2 节,“myisampack &mdash;生成压缩的只读 MyISAM 表”。表选项的差异,比如 AVG_ROW_LENGTH, MAX_ROWS 或 PACK_KEYS 都不重要。

当你创建一个 MERGE 表之时,MySQL 在磁盘上创建两个文件。文件名以表的名字开始,并且有一个扩展名来指明文件类型。一个。frm 文件存储表定义,一个。MRG 文件包含被当作一个来用的表的名字。这些表作为 MERGE 表自身,不必要在同一个数据库中。

你可以对表的集合用 SELECT, DELETE, UPDATE 和 INSERT。你必须对你映射到一个 MERGE 表的这些表有 SELECT, UPDATE 和 DELETE 的权限。

如果你 DROP MERGE 表,你仅在移除 MERGE 规格。底层表没有受影响。

当你创建一个 MERGE 表之时,你必须指定一个 UNION=(list-of-tables) 子句,它说明你要把哪些表当作一个来用。如果你想要对 MERGE 表的插入发生在 UNION 列表中的第一个或最后一个表上,你可以选择地指定一个 INSERT_METHOD 选项。使用 FIRST 或 LAST 值使得插入被相应地做在第一或最后一个表上。如果你没有指定 INSERT_METHOD 选项,或你用一个 NO 值指定该选项。往 MERGE 表插入记录的试图导致错误。

下面例子说明如何创建一个 MERGE 表:

mysql> CREATE TABLE t1 (
    ->    a INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ->    message CHAR(20));
mysql> CREATE TABLE t2 (
    ->    a INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
    ->    message CHAR(20));
mysql> INSERT INTO t1 (message) VALUES ('Testing'),('table'),('t1');
mysql> INSERT INTO t2 (message) VALUES ('Testing'),('table'),('t2');
mysql> CREATE TABLE total (
    ->    a INT NOT NULL AUTO_INCREMENT,
    ->    message CHAR(20), INDEX(a))
    ->    TYPE=MERGE UNION=(t1,t2) INSERT_METHOD=LAST;

注意,一个列在 MERGEN 表中被索引,但没有被宣告为一个 PRIMARY KEY,因为它是在更重要的 MyISAM 表中。这是必要的,因为 MERGE 表在更重要的表中的设置上强制非唯一性。

创建 MERGE 表之后,你可以发出把一组表当作一体来操作的查询:

mysql> SELECT * FROM total;
+---+---------+
| a | message |
+---+---------+
| 1 | Testing |
| 2 | table   |
| 3 | t1      |
| 1 | Testing |
| 2 | table   |
| 3 | t2      |
+---+---------+

注意,你也可以直接从 MySQL 之外直接操作.MRG 文件:

shell> cd /mysql-data-directory/current-database
shell> ls -1 t1 t2 > total.MRG
shell> mysqladmin flush-tables

要重映射一个 MERGE 表到一个不同的 MyISAM 表集,你可以执行下列之一:

·         DROP MERGE 表并且重建它。

·         使用 ALTER TABLE tbl_name UNION=(...) 来改变底层表的列表。

·         改变。MRG 文件,并对 MERGE 表或者所有底层表发出一个 FLUSH TABLE 语句来强制存储引擎去读新的定义文件。

MERGE 表可以帮你解决以下问题:

·         容易地管理一套日志表。比如,你可以把不同月的数据放进分离的表中,用myisampack压缩其中的一些,并随后创建一个 MERGE 表来把它们当作一个来使用。

·         获得更快的速度。你可以以一些标准来分割大的只读表,然后放进不同磁盘上的单个表中。基于此的一个 MERGE 表可比使用大表要快得多。

·         执行更有效的搜索。如果你确切知道要搜索什么,对一些查询你可以只在被分割的表的其中之一来搜索,并且对其它使用 MERGE。你甚至有许多不同的 MERGE 表,它们使用有重叠的表套。

·         执行更有效的修补。修补被映射到一个 MERGE 表中的单个表比修补单个大型表要更轻松。

·         即刻映射许多表成一个。MERGE 表不需要维护它自己的索引,因为它使用大哥表的所用。因此,MERGE 表集合是非常块地创建或重映射。(注意,当你创建一个 MERGE 表之时,即使没有索引被创建,你必须仍然指定索引定义)。

·         如果根据需要或按照批次,你有一组要合起来作为一个大表的表,你应该根据需要对它们创建一个 MERGE 表来替代大表。这样要快得多而且节约大量的磁盘空间。

·         超过操作系统的文件尺寸限制。每个 MyISAM 表都受制于这个限制,但是一个 MyISAM 表的集合则不然。

·         你可以通过定义一个映射到单个表的 MERGE 表来为一个 MyISAM 表创建一个别名或&ldquo;同物异名”。这样做应该没有真实的可察觉的性能影响 (对每个读只有一些间接调用和 memcpy() 调用)。

MERGE 表的缺点:

·         你可以对 MERGE 表使用仅相同的 MyISAM 表。

·         你不能在 MERGE 表中使用很多 MyISAM 功能。比如,你不能在 MERGE 表上创建 FULLTEXT 索引。(当然,你可以在底层 MERGE 表上创建 FULLTEXT 索引,但是你不能用全文搜索来搜索 MERGE 表)。

·         如果 MERGE 表是非临时的,所有底层 MyISAM 表也必须是永久的。如果 MERGE 表是临时的,MyISAM 表可以是任何临时&非临时的混合。

·         MERGE表使用更多的文件描述符。如果是个客户端正使用一个映射到 10 个表的 MERGE 表,服务器使用 (10*10)+10 个文件描述符。(10 个数据文件描述符给 10 个客户端每人一个,并且在客户端之间共享 10 个索引文件描述符)。

·;键读会更慢。当你读一个键的时候,MERGE 存储引擎需要在所有 底层表上发出一个读以检查哪一个接近匹配给定的键。如果你随后做了一个 read-next,MERGE 存储引擎需要搜索读缓冲来找出下一个键。只有当一个键缓冲被耗尽,存储引擎才需要读下一个 键块。这使得 MERGE 键在 eq_ref 搜索中非常慢,但在 ref 搜索中不是太慢。请参阅7.2.1 节,“EXPLAIN 语法 (获取 SELECT 相关信息)” 以获得更多关于 eq_ref 和 ref 的信息。

15.3.1MERGE 表 方面的问题

下列是已知关于 MERGE 表的问题:

·         如果你使用 ALTER TABLE 来把 MERGE 表变为其它表类型,到 底层表的映射就被丢失了。取而代之的,来自底层 MyISAM 表的行被复制到已更换的表中,该表随后被指定新类型。

·         REPLACE不起作用。

·         没有 WHERE 子句,或者在任何被映射到一个打开的 MERGE 表上的任何一个表上的 REPAIR TABLE,TRUNCATE TABLE, OPTIMIZE TABLE 或 ANALYZE TABLE,你不能使用 DROP TABLE, ALTER TABLE, DELETE FROM。如果你这么做了,MERGE 表将仍旧指向原始表,这样产生意外结果。解决这个不足最简单的办法是在执行任何一个这些操作之前发出一个 FLUSH TABLES 语句来确保没有 MERGE 表仍旧保持打开。

·         一个 MERGE 表不能在整个表上维持 UNIQUE 约束。当你执行一个 INSERT,数据进入第一个或者最后一个 MyISAM 表 (取决于 INSERT_METHOD 选项的值)。MySQL 确保唯一 键值在那个 MyISAM 表里保持唯一,但不是跨集合里所有的表。

·         当你创建一个 MERGE 表之时,没有检查去确保底层表的存在以及有相同的机构。当 MERGE 表被使用之时,MySQL 检查每个被映射的表的记录长度是否相等,但这并不十分可靠。如果你从不相似的 MyISAM 表创建一个 MERGE 表,你非常有可能撞见奇怪的问题。

·         在 MERGE 表中的索引的顺序和它的 底层表中的索引应该一样。如果你使用 ALTER TABLE 给一个被用在 MERGE 表中的表添加一个 UNIQUE 索引,然后使用 ALTER TABLE 在 MERGE 表上添加一个非唯一索引,如果在 底层表上已经有一个非唯一索引,对表的索引排序是不同的。(这是因为 ALTER TABLE 把 UNIQUE 索引放在非唯一索引之前以利于重复键的快速检测 )。因此对使用这样索引的表的查询可能返回不期望的结果。

·         在 Windows 中,在一个被MERGE 表使用的表上DROP TABLE 不起作用,因为MERGE 引擎的表映射对 MySQL 的更上层隐藏。因为 Windows 不允许已打开文件的删除,你首先必须 刷新所有 MERGE 表 (使用 FLUSH TABLES) 或在移除该表之前移除 MERGE 表。

对于 MERGE 存储引擎,在http://forums.mysql.com/list.php?93上有一个专门的论坛。

15.4. MEMORY (HEAP) 存储引擎

MEMORY 存储引擎用存在内存中的内容来创建表。这些在以前被认识为 HEAP 表。MEMORY 是一个首选的术语,虽然为向下兼容,HEAP 依旧被支持。

每个 MEMORY 表和一个磁盘文件关联起来。文件名由表的名字开始,并且由一个。frm 的扩展名来指明它存储的表定义。

要明确指出你想要一个 MEMORY 表,可使用 ENGINE 选项来指定:

CREATE TABLE t (i INT) ENGINE = MEMORY;

如它们名字所指明的,MEMORY 表被存储在内存中,且默认使用哈希索引。这使得它们非常快,并且对创建临时表非常有用。可是,当服务器关闭之时,所有存储在 MEMORY 表里的数据被丢失。因为表的定义被存在磁盘上的。frm 文件中,所以表自身继续存在,在服务器重启动时它们是空的。

这个例子显示你如何可以创建,使用并删除一个 MEMORY 表:

mysql> CREATE TABLE test ENGINE=MEMORY
    ->     SELECT ip,SUM(downloads) AS down
    ->     FROM log_table GROUP BY ip;
mysql> SELECT COUNT(ip),AVG(down) FROM test;
mysql> DROP TABLE test;

MEMORY 表有下列特征:

·         给 MEMORY 表的空间被以小块来分配。表对插入使用 100% 动态哈希来。不需要溢出区或额外键空间。自由列表无额外的空间需求。已删除的行被放在一个以链接的列表里,并且在你往表里插入新数据之时被重新使用。MEMORY 表也没有通常与在哈希表中删除加插入相关的问题。

·         MEMORY表可以有多达每个表 32 个索引,每个索引 16 列,以及 500 字节的最大 键长度。

·         MEMORY 存储引擎执行 HASH 和 BTREE 索引。你可以通过添加一个如下所示的 USING 子句为给定的索引指定一个或另一个:

·                CREATE TABLE lookup
·                    (id INT, INDEX USING HASH (id))
·                    ENGINE = MEMORY;
·                CREATE TABLE lookup
·                    (id INT, INDEX USING BTREE (id))
·                    ENGINE = MEMORY;

B 树的一般特征和哈希索引在7.4.5 节,“MySQL 如何使用索引”里描述。

·         你可以在一个 MEMORY 表中有非唯一键。(对哈希索引的实现,这是一个不常用的功能)。

·         你页可以对 MEMORY 表使用 INSERT DELAYED。请参阅13.2.4.2 节,“INSERT DELAYED 语法”

·         如果你在一个有高度键重复的 (许多索引条目包含同一个值)MEMORY 表上有一个哈希索引,对影响键值的表的更新及所有删除都是明显地慢的。这个变慢的程度比例于重复的程度 (或者反比于索引 cardinality)。你可以使用一个 B 树索引来避免这个问题。

·         MEMORY表使用一个固定的记录长度格式。

·         MEMORY 不支持BLOB 或 TEXT 列。

·         MEMORY 支持AUTO_INCREMENT 列和对可包含 NULL 值的 列的索引。

·         MEMORY 表在所有客户端之间共享 (就像其它任何非TEMPORARY 表)。

·         MEMORY表内容被存在内存中,内存是 MEMORY 表和服务器在查询处理之时的空闲中创建的内部表共享。可是,两个类型的表不同在于 MEMORY 表不会遇到存储转换,而内部表是:

-        如果一个内部表变得太大,服务器自动把它转换为一个磁盘表。尺寸限制由 tmp_table_size 系统变量的值来确定。

-        MEMORY 表决不会转换成磁盘表。要确保你不会偶尔做点傻事,你可以设置max_heap_table_size 系统变量给 MEMORY 表加以最大尺寸。对于单个的表,你也可以在 CREATE TABLE 语句中指定一个 MAX_ROWS 表选项。

·         服务器需要足够内存来维持所有在同一时间使用的 MEMORY 表。

·         当你不再需要 MEMORY 表的内容之时,要释放被 MEMORY 表使用的内存,你应该执行 DELETE FROM 或 TRUNCATE TABLE,或者整个地删除表 (使用 DROP TABLE)。

·         当 MySQL 服务器启动时,如果你想开拓 MEMORY 表,你可以使用--init-file 选项。例如,你可以把 INSERT INTO ... SELECT 或 LOAD DATA INFILE 这样的语句放入这个文件中以便从持久稳固的的数据源装载表。请参阅5.3.1 节,“mysqld 命令行选项” and 13.2.5 节,“LOAD DATA INFILE 语法”

·         如果你正使用复制,当主服务器被关闭且重启动之时,主服务器的 MEMORY 表变空。可是从服务器意识不到这些表已经变空,所以如果你从它们选择数据,它就返回过时的内容。自从服务器启动后,当一个 MEMORY 表在主服务器上第一次被使用之时,一个 DELETE FROM 语句被自动写进主服务器的二进制日志,因此再次让从服务器与主服务器同步。注意,即使使用这个策略,在主服务器的重启和它第一次使用该表之间的间隔中,从服务器仍旧在表中有过时数据。可是,如果你使用--init-file 选项于主服务器启动之时在其上推行 MEMORY 表。它确保这个时间间隔为零。

·         在 MEMORY 表中,一行需要的内存使用下列表达式来计算:

·                SUM_OVER_ALL_BTREE_KEYS(max_length_of_key + sizeof(char*) * 4)
·                + SUM_OVER_ALL_HASH_KEYS(sizeof(char*) * 2)
·                + ALIGN(length_of_row+1, sizeof(char*))

ALIGN() 代表 round-up 因子,它使得行的长度为 char 指针大小的确切倍数。sizeof(char*) 在 32 位机器上是 4,在 64 位机器上是 8。

对于 MEMORY 存储引擎,在http://forums.mysql.com/list.php?92上有一个专门的论坛。

15.5. BDB (BerkeleyDB) 存储引擎

15.5.1. BDB 支持的操作系统

15.5.2. 安装 BDB

15.5.3. BDB 启动选项

15.5.4. BDB 表的特征

15.5.5. 修改 BDB 所需 的事宜

15.5.6. BDB 表的限制

15.5.7. 使用 BDB 表时可能发生的错误

Sleepycat Software 给 MySQL 提供 Berkeley DB 事务性存储引擎。这个存储引擎典型被简称为 BDB。对 BDB 存储引擎的支持包括在 MySQL源码分发版里,在 MySQL-Max 二进制分发版里被激活。

BDB 表可能有一个更大的崩溃幸存机会,并且也具有对事务 COMMIT 和 ROLLBACK 操作的能力。MySQL 源码分发版和被补丁过可以与 MySQL 一起工作的 BDB分发版一起提供。你不能使用一个未补丁过的 BDB 版本与 MySQL 一起工作。。

我们在 MySQL AB 上与 Sleepycat 紧密合作工作以保持 MySQL/BDB 接口的质量在高水平。(即使 Berkeley DB 其本身是非常能经受考验和非常可靠的。MySQL 接口仍然被认为是第三等质量的。我们将继续改善和优化它)。

当它达到对所有涉及 BDB 表的问题的支持之时,我们答应负责帮助我们的用户定位问题并创建可重复产生的测试案例。任何这样的测试案例被转交给 Sleepycat,它反过来帮助我们找到并解决问题。因为这是一个二阶段的操作,任何关于 BDB 表的问题我们可能要花比对其它存储引擎稍微更长一点的时间来解决它。可是,我们期望这个过程没有显著的困难,因为 Berkeley DB 代码本身被用在 MySQL 之外许多的应用中。

要获得关于 Berkeley DB 的一般信息,请访问 Sleepycat 网站,http://www.sleepycat.com/

15.5.1BDB 支持的操作系统

当前,我们知道 BDB 存储引擎支持下列操作系统:

·         Linux 2.x Intel

·         Sun Solaris (SPARC and x86)

·         FreeBSD 4.x/5.x (x86, sparc64)

·         IBM AIX 4.3.x

·         SCO OpenServer

·         SCO UnixWare 7.1.x

·         Windows NT/2000/XP

BDB 不支持下列操作系统:

·         Linux 2.x Alpha

·         Linux 2.x AMD64

·         Linux 2.x IA-64

·         Linux 2.x s390

·         Mac OS X

注释:前一个列表还不完全,我们收到更多信息时我们会更新它。

如果你从支持 BDB 表的源码建立的 MySQL,但是,当你启动mysqld之时,发生下列错误,这意味着对你的架构 BDB 不被支持:

bdb: architecture lacks fast mutexes: applications cannot be threaded
Can't init databases

在这种情况下,你必须重建 MySQL 不带 BDB 表支持或用--skip-bdb 选项启动服务器。

15.5.2. 安装BDB

如果你已经下载一个包括对 Berkeley DB 支持的的二进制版的 MySQL, 只要简单地按照通常的二进制分发版安装指令。(MySQL-Max 分发版包括 BDB 支持)。

如果你从源码建立 MySQL,你可以在其它任何常用选项之外用--with-berkeley-db 选项运行configure来允许支持 BDB。下载一个 MySQL 5.1 分发版,改变位置到它的最顶层目录,然后运行下面命令:

shell> ./configure --with-berkeley-db [other-options]

更多信息,请参阅2.7 节,“在其它类似 Unix 系统上安装 MySQL”, 5.1.2 节,“mysqld-max扩展 MySQL 服务器”,和2.8 节,“使用源码分发版版安装 MySQL”

15.5.3BDB启动选项

下列对mysqld的选项可被用来改变 BDB 存储引擎的行为:

·         --bdb-home=path

BDB 表的基础目录。这应该和你为--datadir 使用的目录相同。

·         --bdb-lock-detect=method

BDB 锁定检测方式。选项值应该为 DEFAULT, OLDEST, RANDOM 或 YOUNGEST。

·         --bdb-logdir=path

BDB 日志文件目录。

·         --bdb-no-recover

不在恢复模式启动 Berkeley DB。

·         --bdb-no-sync

不同步刷新 BDB 日志。这个选项不被赞成,取而代之地使用--skip-sync-bdb-logs(请参阅对--sync-bdb-logs 的描述)。

·         --bdb-shared-data

以多处理模式启动 Berkeley DB。(初始化 Berkeley DB 之时,不要使用 DB_PRIVATE)。

·         --bdb-tmpdir=path

BDB 临时文件目录。

·         --skip-bdb

禁止 BDB 存储引擎。

·         --sync-bdb-logs

同步刷新 BDB 日志。这个选项默认被允许,请使用--skip-sync-bdb-logs 来禁止它。

请参阅5.3.1 节,“mysqld命令行选项”

如果你使用--skip-bdb 选项,MySQL 不初始化 Berkeley DB 库,而且这样节省大量的内存。尽管如此,如果你使用这个选项,你不能使用 BDB 表。如果你试着创建一个 BDB 表,MySQL 取而代之地创建一个 MyISAM。

通常,如果你象使用 BDB 表,你应该不用--bdb-no-recover 选项启动mysqld。可是,如果 BDB 日志被破坏则当你试着启动mysqld时,上述办法启动服务器可能导致问题。请参阅2.9.2.3 节,“MySQL 服务器的启动和故障诊断排除”

使用 bdb_max_lock 变量,你可以指定在 BDB 表上被激活的锁定的最大数目。默认值是 10,000。当你执行长事务或当mysqld不得不检查许多行来执行一个查询之时,如果发生如下错误,你应该增加这个数目:

bdb: Lock table is out of available locks
Got error 12 from ...

如果你正使用大型多语句事务,你可能也想改变 binlog_cache_size 和 max_binlog_cache_size 变量。请参阅5.11.3 节,“二进制日志”

也请参阅5.3.3 节,“服务器系统变量”

15.5.4BDB 表的特征

每个 BDB 表用两个文件被存在磁盘上。文件的名字用表的名字做开头,并且有一个扩展名来指明文件类型。一个。frm 文件存储表定义,一个。db 文件包含表数据和索引。

要明确指出你想要一个 BDB 表,用 ENGINE 或 TYPE 表选项来指明:

CREATE TABLE t (i INT) ENGINE = BDB;
CREATE TABLE t (i INT) TYPE = BDB;

BerkeleyDB 是用 ENGINE 或者 TYPE 选项的 BDB 存储引擎的&ldquo;同义词”。

BDB 存储引擎提供事务性表,你使用这些表的方法取决于 autocommit 模式:

·         如果你正运行着,同时随着 autocommit 的被允许 (这是默认的),对 BDB 表的改变被立即提交并且不能被 回滚。

·         如果你正运行着,同时随着 autocommit 的被禁止,改变不变成永久的直到你执行一个 COMMIT 语句。作为提交的替代,你可以执行 ROLLBACK 来忘记改变。

你可以用 BEGIN WORK 语句开始一个事务来挂起 autocommit,或者用 SET AUTOCOMMIT=0 来明确禁止 autocommit。

请参阅13.4.1 节,“START TRANSACTION, COMMIT 和 ROLLBACK 语法”

BDB 存储引擎有下列特征:

·         BDB 表可以有多达每表 31 个索引,每个索引 16 列,并且 1024 字节的最大 键尺寸。

·         MySQL 在每个 BDB 表中需要一个 PRIMARY KEY 以便每一行可以被唯一地识别。如果你不明确创建一个,MySQL 为你创建并维持一个隐藏的 PRIMARY KEY。隐藏的 键有一个 5 字节的长度,并且为每个插入的企图而被增加。这个键不出现在 SHOW CREATE TABLE 或 DESCRIBE 的输出之中。

·         PRIMARY KEY 比任何其它索引都要快,因为 PRIMARY KEY 被与行的数据一起存储。其它索引被存储为键数据+PRIMARY KEY,所以保持 PRIMARY KEY 尽可能地短以节约磁盘空间并获得更好速度是重要的。

这个行为类似于 InnoDB 的,在其中较短的 primary keys 不仅在主索引也在第二索引节约空间 。

·         如果在 BDB 表中,你访问的所有列是同一索引的一部分或主键的一部分,MySQL 可以执行查询而不访问确实的行。在一个 MyISAM 表中,只有 列是同一索引的一部分之时,才可以这么做。

·         连续扫描比对 MyISAM 表的扫描更慢,因为在 BDB 表中的数据被存储在 B 树而不是在分离的数据文件中。

·        ;键值象 MyISAM 表中的 键值一样不是前缀或后缀压缩的。换句话说,在 BDB 表中键信息只比在 MyISAM 表中稍微多占据一点空间。

·         在 BDB 表中经常有洞允许你在索引树的中间插入新行。这个使得 BDB 表比 MyISAM 表稍微大一些

·         SELECT COUNT(*) FROM tbl_name对 BDB 表很慢,因为在该表中没有行计数被维持。

·         优化器需要知道表中的大概行数。MySQL 通过计数插入以及在每个 BDB 表中的单独片断内维持它来解决了问题。如果你不发出大量的 DELETE 或 ROLLBACK 语句,这个数对 MySQL 优化器来说是足够精确了。可是,MySQL 仅在关闭的时候才存储这个数,所以,如果服务器被意外地终止,这个数可能是不正确的。即使数不是 100% 正确,它不是 明确的。你可以使用 ANALYZE TABLE 或者 OPTIMIZE TABLE 语句来更新行计数。请参阅13.5.2.1 节,“ANALYZE TABLE 语法”13.5.2.5 节,“OPTIMIZE TABLE 语法”

·         BDB 表上的内部锁定在页面级别上做。

·         LOCK TABLES在 BDB 表上就想在其它表上一样工作。如果你不使用 LOCK TABLES,MySQL 对该表发出一个内部多重写锁定 (一个不阻止其它作者的锁定) 来确保即使另一个线程发出一个表锁定,该表也被恰当地锁定了。

·         要能够回滚一个事务,BDB 存储引擎维护日志文件。多实现最高性能,你可以使用--bdb-logdir 选项来把 BDB 日志放在不同的磁盘上,而不是放在数据库被放置的那个磁盘。

·         每次新 BDB 日志文件被启动之时,MySQL 执行一个检查点,并且删掉当前事务不需要的任何 BDB 日志文件。你也可以在任何时候使用 FLUSH LOGS 来给 Berkeley DB 表设置检查点。

对灾难恢复,你应该使用表备份加 MySQL 的二进制日志,请参阅5.9.1 节,“数据库备份”

警告:如果你删除仍在使用中的旧日志文件,BDB 根本不能做恢复,并且如果有些事不对,你可能会丢失数据。

·         应用程序必须总是被准备来处理情况,即 BDB 表任何的改变可能导致一个自动回滚并且任何读可能会带着一个死锁错误而失败。

·         如果你在 BDB 表内遇到磁盘满,你得到一个错误 (可能是错误 28),并且事务应该回滚。这与 MyISAM 表相反,对于 MyISAM 表,mysqld在继续之前等待足够的自由磁盘空间。

15.5.5. 修改BDB 所需的 事宜

·         同时打开许多 BDB 表可能是非常慢的。如果你要使用 BDB 表,你不应使用非常大的表缓存 (例如,大于 256) 并且当你使用mysql客户端之时,你应该使用--no-auto-rehash 选项。

·         SHOW TABLE STATUS 不为 BDB 表提供一些信息:

·                mysql> SHOW TABLE STATUS LIKE 'bdbtest'\G
·                *************************** 1. row ***************************
·                           Name: bdbtest
·                         Engine: BerkeleyDB
·                        Version: 10
·                     Row_format: Dynamic
·                           Rows: 154
·                 Avg_row_length: 0
·                    Data_length: 0
·                Max_data_length: 0
·                   Index_length: 0
·                      Data_free: 0
·                 Auto_increment: NULL
·                    Create_time: NULL
·                    Update_time: NULL
·                     Check_time: NULL
·                      Collation: latin1_swedish_ci
·                       Checksum: NULL
·                 Create_options:
·                        Comment:

·         优化性能。

·         改变为对表扫描操作使用无页面锁定。

15.5.6对 BDB 表的限制

下列表说明使用 BDB 表之时你必须要遵从的限制:

·         每个 BDB 表在。db 文件里存储文件被创建之时到该文件的路径。这个被做来允许在支持 symlinks 的多用户环境里检测锁定。因此,从一个数据库目录往另一个目录移动 BDB 表是不能的。

·         当制作 BDB 表的备份之时,你必须要么使用mysqldump要么做一个包含对每个 BDB 表的文件 (.frm 和。db 文件) 及 BDB 日志文件的备份。BDB 存储引擎在它的日志文件存储未完成的事务以及要求它们在mysqld启动的时候被呈出来。BDB 日志在数据目录里,具有 log.XXXXXXXXXX(10 位数字) 形式名字的文件。

·         如果允许 NULL 值的列有唯一的索引,只有单个 NULL 值是被允许的。这不同于其它存储引擎。

15.5.7. ;使用BDB 表时可能发生的错误

·         如果你升级之后启动mysqld时发生下列错误,它意味着新 BDB 版本不支持旧日志文件格式:

·                bdb:  Ignoring log file: .../log.XXXXXXXXXX:
·                unsupported log version #

在这种情况下,你必须删除从数据目录所有 BDB 日志 (名字为 log.XXXXXXXXXX 这样格式的文件) 并重新启动mysqld。我们也推荐你随后用mysqldump --opt来转储你的 BDB 表,移除表,并且从转储文件恢复它们。

·         如果 autocommit 模式被禁止,你移除在另一个事务中被参考的 BDB 表,你会从你的 MySQL 错误日志得到如下的错误信息:

·                001119 23:43:56  bdb:  Missing log fileid entry
·                001119 23:43:56  bdb:  txn_abort: Log undo failed for LSN:
·                                       1 3644744: Invalid

这不是一个致命错误,但是知道问题被解决,我们推荐你不要移除 BDB 表,除了 autocommit 模式被允许之时。(修复不是微不足道的)。

15.6. EXAMPLE存储引擎

EXAMPLE 存储引擎是一个不做任何事情的存根引擎。它的目的是作为 MySQL 源代码中的一个例子,用来演示如何开始编写一个新存储引擎。 同样,它的主要兴趣是对开发者。

要对 EXAMPLE 引擎检查源 码,请查看 MySQL 源码分发版的 sql/examples 目录。

要允许这个存储引擎,使用在建立 MySQL 之时使用--with-example-storage-engine 选项来configure

当你创建一个 EXAMPLE 之时,服务器创建在数据库目录创建一个表定义文件。文件由表名字开始,并由一个。frm 的扩展名。没有其它文件被创建。没有数据被存储进表或者从中取回。

mysql> CREATE TABLE test (i INT) ENGINE = EXAMPLE;
Query OK, 0 rows affected (0.78 sec)
 
mysql> INSERT INTO test VALUES(1),(2),(3);
ERROR 1031 (HY000): Table storage engine for 'test' doesn't have this option
 
mysql> SELECT * FROM test;
Empty set (0.31 sec)

EXAMPLE 存储引擎不支持编索引。

15.7. FEDERATED存储引擎

15.7.1. 安装 FEDERATED 存储引擎

15.7.2. FEDERATED 存储引擎的 介绍

15.7.3. 如何使用 FEDERATED 表

15.7.4. FEDERATED 存储引擎的 局限性

FEDERATED 存储引擎访问在远程数据库的表中的数据,而不是本地的表。

FEDERATED 存储引擎仅在-MAX 版的 MySQL 上可用。

要为 FEDERATED 引擎检查 源码,请查看 MySQL 源码分发版的 sql 目录。

对于 FEDERATED 存储引擎,在http://forums.mysql.com/list.php?105上有一个专门的论坛。

15.7.1. 安装FEDERATED 存储引擎

要允许这个存储引擎,当你构建 MySQL 时请使用--with-federated-storage-engine 来configure

15.7.2FEDERATED 存储引擎的描述

当你创建一个 FEDERATED 表的时候,服务器在数据库目录创建一个表定义文件。文件由表的名字开始,并有一个。frm 扩展名。无其它表被创建,因为实际的数据在一个远程数据库上。这不同于为本地表工作的存储引擎的方式。

对于本地的数据录表,数据文件是本地的。例如,如果你创建一个名为 user 的 MyISAM 表,MyISAM 处理器创建一个名为 users.MYD 的数据文件。对本地表读,插入,删除和更新在本地数据文件里的数据的处理器和记录被以对处理器的特定格式存储。 为了读记录,处理器必须把数据分解进列。为了写记录,列值必须被转换到被处理器使用的行格式并且被写进本地的数据文件。

使用 MySQL FEDERATED 存储引擎,没有对表的本地数据文件 (比如,没有。MYD 文件)。取而代之地,一个远程数据库存储那些正常地应该在表中的数据。这使得 MySQL 客户端 API 来读,删除,更新和插入数据的使用成为必要。数据取回被通过 SELECT * FROM tbl_name SQL 语句来初始化。要读这个结果,通过使用 mysql_fetch_row() C API 函数,行被一次取一个,然后从 SELECT 结果包中的列转换成 FEDERATED 处理器期望的格式。

基本流程如下:

1.    SQL 调用被本地发布

2.    MySQL 处理器 API (数据以处理器格式)

3.    MySQL 客户端 API (数据被转换成 SQL 调用)

4.    远程数据库-> MySQL 客户端 API

5.    转换结果包 (如果有的话) 到处理器格式

6.    处理器 API -> 结果行或受行影响的对本地的计数

15.7.3. 如何使用FEDERATED

使用 FEDERATED 表的步骤是非常简单的。通常,你运行两个服务器,要么在同一个主机上,要么在不同主机上。(一个 FEDERATED 表使用其它被同一服务器管理的表也是可能的。虽然只有极少的点要这么做)。

首先,你必须在你想要用 FEDERATED 表访问的远程服务器上有一个表。假设,远程的表在 FEDERATED 数据库中并且被如下定义:

CREATE TABLE test_table (
    id     int(20) NOT NULL auto_increment,
    name   varchar(32) NOT NULL default '',
    other  int(20) NOT NULL default '0',
    PRIMARY KEY  (id),
    KEY name (name),
    KEY other_key (other)
)
ENGINE=MyISAM
DEFAULT CHARSET=latin1;

ENGINE 表选项可能命名任何存储引擎,该表需要不是一个 MyISAM 表。

接着,在本地服务器上为访问远程表创建一个 FEDERATED 表:

CREATE TABLE federated_table (
    id     int(20) NOT NULL auto_increment,
    name   varchar(32) NOT NULL default '',
    other  int(20) NOT NULL default '0',
    PRIMARY KEY  (id),
    KEY name (name),
    KEY other_key (other)
)
ENGINE=FEDERATED
DEFAULT CHARSET=latin1
CONNECTION='mysql://root@remote_host:9306/federated/test_table';

(注意: CONNECTION 替代 用在先前版本的 MySQL 里的 COMMENT)。

除了 ENGINE 表选项应该是 FEDERATED,并且 CONNECTION 表选项是给 FEDERATED 指明如何连接到远程服务器上的连接字符串之外, 这个表的结构必须完全与远程表的结构相同。

FEDERATED 引擎仅创建在已联盟数据库中的 test_table.frm 文件。

远程主机信息指明本地服务器要连接到的远程服务器,数据库和表信息指明哪一个远程表要被作为数据文件来用。在这个例子中。远程服务器被指定来作为远程主机在 9306 端口上运行,所以你要启动服务器,让它监听 9306 端口。

在 CONNECTION 选项中的连接字符串的一般形式如下:

scheme://user_name[:password]@host_name[:port_num]/db_name/tbl_name

只有 mysql 在这一点被支持为scheme,密码和端口号时可选的。

这里有一些连接字符串的例子:

CONNECTION='mysql://username:password@hostname:port/database/tablename'
CONNECTION='mysql://username@hostname/database/tablename'
CONNECTION='mysql://username:password@hostname/database/tablename'

为指定连接字符串使用 CONNECTION 是非可选,并且在将来可能会改变。当你使用 FEDERATED 表的时候,要记得这个,因为这意味着当将来发生那种改变之时,可能被要求。

因为任何被用的密码作为纯文本被存在连接字符串中,它可以被任何使对 FEDERATED 表使用 SHOW CREATE TABLE 或 SHOW TABLE STATUS 的用户,或者在 INFORMATION_SCHEMA 数据库中查询 TABLES 表的用户看见。

对于 FEDERATED 存储引擎,在http://forums.mysql.com/list.php?105上有一个专门的论坛。

15.7.4FEDERATED 存储引擎的局限 性

FEDERATED 支持及不支持的如下:

·         在第一个版本中,远程服务器必须是一个 MySQL 服务器。FEDERATED 对其它数据库引擎的支持可能会在将来被添加。

·         FEDERATED 表指向的远程表在你通过 FEDERATED 表访问它之前必须存在。

·         一个 FEDERATED 表指向另一个 FEDERATED 表是可能的,但是你必须小心不要创建一个循环。

·         没有对事务的支持。

·         如果远程表已经改变,对 FEDERATED 引擎而言是没有办法知道的。这个的原因是因为这个表必须象数据文件一样工作,除了数据库其它任何都不会被写入。如果有任何对远程数据库的改变,本地表中数据的完整性可能会被破坏。

·         FEDERATED 存储引擎支持 SELECT, INSERT, UPDATE, DELETE 和索引。它不支持 ALTER TABLE, DROP TABLE 或任何其它的数据定义语言语句。当前的实现不使用预先准备好的语句。

·         执行使用 SELECT, INSERT, UPDATE 和 DELETE,但不用 HANDLER。

·         FEDERATED 表不能对查询缓存不起作用。

这些限制中的一些在 FEDERATED 处理机的将来版本可能被消除。

15.8. ARCHIVE存储引擎

ARCHIVE 存储引擎被用来以非常小的覆盖区存储大量无索引数据。 

要允许这个存储引擎,在建立 MySQL 之时使用--with-archive-storage-engine 选项来configure。如果这个存储引擎可带这个语句使用,你可以看见:

mysql> SHOW VARIABLES LIKE 'have_archive';

当年创建一个 ARCHIVE 表,服务器在数据库目录创建一个表定义文件。文件由表的名字开始,并由一个。frm 的扩展名。存储引擎创建其它文件,所有都有由表名字开头的名字。数据和元数据文件有扩展名。ARZ 和。ARM。一个。ARN 文件可能在优化操作中出现。

ARCHIVE 引擎仅支持 INSERT 和 SELEC(无删除,替换或更新)。它支持 ORDER BY 操作,BLOB 域,以及基本地所有数据类型,除了几何数据类型 (请参阅19.4.1 节,“MySQL 空间数据类型”)。ARCHIVE 引擎使用行级锁定。

存储: 当记录被插入时,它们被压缩。ARCHIVE 引擎使用zlib无损数据压缩。OPTIMIZE TABLE 的使用可以分析表,并把它打包为更小的格式 ( 使用 OPTIMIZE TABLE 的原因,往下看)。引擎页支持 CHECK TABLE。有数种被使用的插入类型:

·         直接插入 INSERT 之时把一行推仅压缩缓冲,并且缓冲在它需要的时候刷新。到缓冲的插入被锁定保护。一个 SELECT 强制一个 刷新发生,除非进来的唯一插入是 INSERT DELAYED(那些刷新如同需要一样)。请参阅13.2.4.2 节,“INSERT DELAYED 语法”

·         块插入旨在它完成后可见,除非其它插入在同一时间发生,在这种情况下,块插入可以被部分看见。一个 SELECT 不会导致一个块插入的 刷新,除非 SELECT 在被装载时发生一个正常插入。

取回: 在取回时,记录根据需要被解压缩,没有行缓存。一个 SELECT 操作执行完全表格扫描:当一个SELECT 发生之时,它找出当前有多少行可用,并读行的数量。SELECT 被当作持续读来执行。注意,许多 SELECT 语句在插入过程中会损坏压缩,除非块插入或者延迟的插入被使用。要修复任何已发生压缩问题,你可以总是做 OPTIMIZE TABLE(也支持 REPAIR TABLE)。被 SHOW TABLE STATUS 报告的行数总是正确的。请参阅13.5.2.6 节,“REPAIR TABLE 语法”13.5.2.5 节,“OPTIMIZE TABLE 语法”13.5.4.18 节 ,“SHOW TABLE STATUS 语法”

对于 ARCHIVE 存储引擎,在 http://forums.mysql.com/list.php?112 上有专门论坛。

15.9. CSV存储引擎

CSV 存储引擎使用逗号分隔值格式的文本文件存储数据。

要允许使用这个存储引擎,当你建立 MySQL 之时,使用--with-csv-storage-engine 选项来configure

当你创建一个 CSV 表之时,服务器在数据库目录创建一个表定义文件。文件由表的名字开始,并且由一个。frm 的扩展名。存储引擎也创建一个数据文件。它的名字由表的名字开始,并且有一个。CSV 的扩展名。数据文件是无格式文本文件。当你把数据存储进表时,存储引擎用 CSV 格式把它存进数据文件。

mysql> CREATE TABLE test(i INT, c CHAR(10)) ENGINE = CSV;
Query OK, 0 rows affected (0.12 sec)
 
mysql> INSERT INTO test VALUES(1,'record one'),(2,'record two');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0
 
mysql> SELECT * FROM test;
+------+------------+
| i    | c          |
+------+------------+
|    1 | record one |
|    2 | record two |
+------+------------+
2 rows in set (0.00 sec)

如果你检查在执行前述语句所创建的数据库目录里的 test.CSV 文件,它的内容应该看起来象如下所示:

"1","record one"
"2","record two"

CSV 存储引擎不支持索引。

15.10. BLACKHOLE存储引擎

BLACKHOLE 存储引擎就像&ldquo;黑洞&rdquo;一样,它接收数据但丢弃它而不是存储它。取回总是返回空集:

mysql> CREATE TABLE test(i INT, c CHAR(10)) ENGINE = BLACKHOLE;
Query OK, 0 rows affected (0.03 sec)
 
mysql> INSERT INTO test VALUES(1,'record one'),(2,'record two');
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0
 
mysql> SELECT * FROM test;
Empty set (0.00 sec)

当你创建一个 BLACKHOLE 表的时候,服务器在数据库目录创建一个表定义文件。文件用表的名字开头,并且有一个。frm 扩展名。没有其它文件关联到这个表格。

BLACKHOLE 存储引擎支持所有种类的索引。

要允许这个存储引擎,在你建立 MySQL 之时使用--with-blackhole-storage-engine 选项来configure。BLACKHOLE 存储引擎在 MySQ 供应的服务器二进制版里可以找到;通过查看 SHOW ENGINES 或 SHOW VARIABLES LIKE 'have%'的输出,你可以确定你的版本是否支持这个引擎。

到 BLACKHOLE 表的插入不存储任何数据,但如果二进制日志被允许,SQL 语句被写入日志 (并被复制到从服务器)。这可以有用帮助地作为重复器或过滤器机制。例如,假设你的应用需要从服务器侧的过滤规则,但传输所有二进制日志数据到从服务器首先导致过多交通的结果。在这种情况下,在主服务器主机上建立一个伪从服务器进程,它的存储引擎是 BLACKHOLE,描述如下:

Replication using BLACKHOLE
        for filtering

主服务器写它的二进制日志。伪 mysqld 进程作为从服务器,应用期望的 replicate-do 和 replicate-ignore 规则的合并,并且写一个新的,被过滤的属于它自己的二进制日志 。(请参阅6.8 节,“复制启动选项”)。这个已过滤日志被提供给从服务器。

既然伪进程不确实地存储任何数据,只有很小的由在复制主服务器主机上额外的 mysqld 进程招致的处理开支。这个类型的建立可以用额外复制从服务器来重复。

其它可能对 BLACKHOLE 存储引擎的使用包括:

·         转储文件语法的验证。

·         来自二进制日志记录的开销测量,通过比较允许二进制日志功能的 BLACKHOLE 的性能与禁止二进制日志功能的 BLACKHOLE 的性能。

·         因为 BLACKHOLE 本质上是一个“no-op&rdquo;存储引擎,它可能被用来查找与存储引擎自身不相关的性能瓶颈。


这是 MySQL 参考手册的翻译版本,关于 MySQL 参考手册,请访问dev.mysql.com。 原始参考手册为英文版,与英文版参考手册相比,本翻译版可能不是最新的。