<< Back to man.ChinaUnix.net

4 建立 InnoDB 表

假设你已以 mysql test 命令方式运行了 MySQL 客户端程序。为了建立一个 InnoDB 格式的表你必须在 SQL 命令中指定 TYPE = InnoDB

CREATE TABLE CUSTOMER (A INT, B CHAR (20), INDEX (A)) TYPE = InnoDB;
	

这个 SQL 命令将在 my.cnf 中设定的 InnoDB 数据文件中创建一个表和一个列 A 的索引,同时将在 MySQL 数据目录下的 test 中建立一个名为 CUSTOMER.frm 的文件。在内部,InnoDB 将在它自己的数据字典(data dictionary)中添加表“test/CUSTOMER”的进入点。这样你可以在其它的 MySQL 数据库中建立一个同样名为 CUSTOMER 的数据表,它将不会与 InnoDB 中的表冲突。

可以通过发出 MySQL 的查看表状态命令查看任何一个 InnoDB 类型表以查看 InnoDB 数据文件的剩余空间。SHOW 输出信息中的表注释部分将显示数据文件的剩余空间。示例:

SHOW TABLE STATUS FROM test LIKE 'CUSTOMER'
	

注意:SHOW 给出的 InnoDB 表统计只是近似的:他们被 SQL 优化。然而,表和索引的 reserved sizes in bytes 是精确的。

要特别注意不要在 InnoDB 数据库中手动添加与删除“.frm” 文件:使用 CREATE TABLE DROP TABLE 命令。 InnoDB 有它自己的内部数据字典(data dictionary),如果 MySQL的“.frm” 文件与  InnoDB 内部的数据字典不“同步”将产生一个错误。

4.1 如何通过不同的 APIs 在 InnoDB 中使用事务

默认的,MySQL 总是启动自动提交你运行的每个 SQL 语句的自动提交(autocommit)模式创建每的新连接。为了使用事务,可以使用 SQL 命令 SET AUTOCOMMIT = 0 关闭自动提交(autocommit)开关,使用 COMMIT ROLLBACK 来提交事务和回滚事务。如果你想保留 autocommit 开关打开,你可以将事务放入 BEGIN COMMIT ROLLBACK之间。

heikki@hundin:~/mysql/client> 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)) TYPE = 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>
	

 

通过 APIs (比如 PHP, Perl DBI/DBD, JDBC, ODBC, 或 MySQL 的标准 C 调用接口),发送一个事务控制语句(比如 "COMMIT" )到 MySQL 服务器可以如同其它的 SQL 语句。例如:"SELECT...""INSERT..."。 APIs often contain separate special commit-transaction methods, 但是 MySQL 对事务的支持还相对比较年轻,他们并不是在所有版本的 APIs 上均能工作的。

4.2 将 MyISAM 表转换到 InnoDB 类型

重要提醒:不可以将 MySQL 系统表比如 'user' 'host' 转换成 InnoDB 类型。系统表必须为 MyISAM 类型。

 

如果你希望所有你新建的表是 InnoDB 类型,从 MySQL version 3.23.43 开始,你可以在你的 my.cnfmy.ini 文件的 [mysqld] 项下加入下列一行

default-table-type=innodb

 

InnoDB 没有一个特殊的分册索引建立优化机制。因此不支持先 export/import 表然后创建索引。最快的办法就是直接将表类型改变为 InnoDB 类型或直接插入数据,这就是说,使用 ALTER TABLE ... TYPE=INNODB 或新建一个空的具有相同结构的 InnoDB 表,然后使用 INSERT INTO ... SELECT * FROM .... 插入数据。

如果有 UNIQUE 约束,从 3.23.52 开始为了提高插入速度你可以关闭唯一性检查:

SET UNIQUE_CHECKS=0;
	
对于较大的表,在向 InnoDB 中插入时会使用索引缓冲区来合并辅助索引记录来分批插入,这将减少许多磁盘的 I/O。

 

为了更好地控制插入过程,最好将大的表分批插入:

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

等全部数据插入完毕后,可以重新命名表名。

在插入过程中,要将 InnoDB 的缓冲池(buffer pool)设大一点以减少磁盘 I/O,然而不要超过物理内存的 80 %。同时还应该将日志文件和日志缓冲加大。

需要注意的是不要让表空间用尽: InnoDB 表比 MyISAM 表使用更多的内存。如果 ALTER TABLE 用光了表空间,将会回滚,如果磁盘不够的话,这个过程将持续几个小时。在向 InnoDB 中插入时会使用索引缓冲区来合并辅助索引记录来分批插入,这将减少很多磁盘 I/O。而在回滚中并不使用这种机制,这将比前者多使用 30 倍的时间。

为了退出失控的回滚,如果在你的导入表中没有什么重要的数据,从 3.23.53 和 4.0.3 开始可以使用技巧退出回滚,请查看 6.1 退出失控的回滚(stop the runaway rollback)。

4.3 外键约束

从 3.23.43b 开始 InnoDB 支持外键约束特性。InnoDB 表类型第一次为 MySQL 提供了外键约束以保证你的数据完整性。

InnoDB 中外键约束的定义语法如下所示:


[CONSTRAINT symbol] FOREIGN KEY [id] (index_col_name, ...)
                 REFERENCES table_name (index_col_name, ...)
                 [ON DELETE {CASCADE | SET NULL | NO ACTION
                                       | RESTRICT}]
                 [ON UPDATE {CASCADE | SET NULL | NO ACTION
                                       | RESTRICT}]                      
	
两个表必须为 InnoDB 类型,外键和被引用键(referenced key)必须是索引中的第一(FIRST)列。InnoDB 不会自动为外键和被引用键建立索引,必须明确创建它们。

 

外键与对应的被引用键在 InnoDB 内必须 有相似的内部数据类型,以便他们不需要一个类型转换就可以进行比较。 整型(Integer)字段的长度与有符号类型(signedness)必须一致。 字符型则不需要一致。如果指定了一个 SET NULL 动作,那你必须要确定 子表中的对应字段没有定义为 NOT NULL

如果 CREATE TABLE 给出 1005 号错误,错误信息字符串提示错误号(errno) 150,那么就是因为外键约束未被正确建立而导致表创建失败。同样的,如果一条 ALTER TABLE 失败而返回错误号 150,那就意味着 altered table 未能正确定义一个外键。从 4.0.13 开始,你可以通过使用 SHOW INNODB STATUS 来查看服务器是最后一条 InnoDB 的外键错误的详细说明。

从 3.23.50 开始,InnoDB 不再在允许 NULL 值外键或被引用键上检查外键约束。

与 SQL 标准不一致: if in the parent table there are several rows which have the same referenced key value, then InnoDB acts in foreign key checks like the other parent rows with the same key value would not exist. For example, if you have defined a RESTRICT type constraint, and there is a child row with several parent rows, InnoDB does not allow the deletion of any of those parent rows.

从 3.23.50 开始,可能联合 ON DELETE CASCADE ON DELETE SET NULL 子句与外键约束一同作用。相应的 ON UPDATE 选项将从 4.0.8 开始支持。如果 ON DELETE CASCADE 被指定,当主表中的记录行被删除时,InnoDB 将自动删除子表中被引用键值与主表中相对应的外键值相同的记录。如果 ON DELETE SET NULL 被指定,子表中的外键对应行将被设置为 NULL 值。

与 SQL 标准不一致: if ON UPDATE CASCADE or ON UPDATE SET NULL recurses to update the SAME TABLE it has already updated during the cascade, it acts like RESTRICT. This is to prevent infinite loops resulting from cascaded updates. A self-referential ON DELETE SET NULL, on the other hand, works starting from 4.0.13. A self-referential ON DELETE CASCADE has always worked.

示例:


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;
	

 

从 3.23.50 开始,InnoDB 允许通过下面的方法给一个表添加一个外键约束:


ALTER TABLE yourtablename
         ADD [CONSTRAINT symbol] FOREIGN KEY [id] (...)
         REFERENCES table_name (index_col_name, ...)
                     [ON DELETE {CASCADE | SET NULL | NO ACTION
                                            | RESTRICT}]
                     [ON UPDATE {CASCADE | SET NULL | NO ACTION
                                            | RESTRICT}]
	
记住首先要建立必要的索引,尽管可以通过 ALTER TABLE 为一个表建立一个自参考(self-referential)的外键。

从 4.0.13 开始,InnoDB 支持

ALTER TABLE DROP FOREIGN KEY internally_generated_foreign_key_id
当你需要删除一个外键时可以使用 SHOW CREATE TABLE 来查看 internally generated foreign key id。

如果要导入表的几个转储(dump),而数据并没有按外键排序,从 3.23.52 和 4.0.3 开始,可以在导入时关闭外键检查

SET FOREIGN_KEY_CHECKS=0;
	
这就允许以任何顺序导入数据,同时提高导入速度。

 

从 3.23.50 开始,InnoDB 语法分析器(parser)允许你 backquotes around table 以及将列名放入 FOREIGN KEY ... REFERENCES ... 子句中。从 4.0.5 开始,InnoDB 语法分析器能处理 my.cnf 文件可能设置的 lower_case_table_names

在小于 3.23.50 的版本中,InnoDB 任何 ALTER TABLECREATE INDEX 均不能在使用在有外键约束或被引用键约束的表上:任何 ALTER TABLE 都将删除表中定义的外键约束。不能再使用 ALTER TABLE 来任何一个表,只有通过 DROP TABLE CREATE TABLE 来修改。当 MySQL 执行一个 ALTER TABLE 时,在内部处理上是通过 RENAME TABLE 来实现的,这将引起外键约束对表的引用混乱。同样 CREATE INDEX 语句也是作为 ALTER TABLE来处理的,也不能用于外键约束的表。

当 InnoDB 进行外键检查时会对主表与子表数据加行锁。nnoDB 会立即检查外键约束:检查不会等到事务提交。

InnoDB 允许你 drop 任何表,即使这样会打破外键,这样操作的结果就是约束也被 drop 了。

InnoDB 允许你撤消(drop)任何表,即使这样会打破被引用表的外键约束。 当你撤消一个表时约束也同时被撤销了。

如果重新创建一个被撤消的表,必须参考原有定义建立一致的外键约束。 必须有正确的列我与类型。必须在引用键上有索引。如果不符合上面的条件,MySQL 将返回1005 号错误,错误信息字符串提示错误号(errno) 150。

从 3.23.50 开始,通过下列指令可以使 InnoDB 返回表的外键约束定义

SHOW CREATE TABLE yourtablename
	
还可以通过 mysqldump 将表的完整定义转储到文件中,当然包括外键定义。

 

还可以通过下面的指令列出表 T 的外键约束:

SHOW TABLE STATUS FROM yourdatabasename LIKE 'T'
	
外键约束将会在表注释中列出。

 

4.4 自增列(auto-increment)是如何在 InnoDB 中工作的

如果表有一个自增(auto-increment)列,那么 InnoDB 表处理系统将它的数据字典中包含一个特别的计数器用以记录自增列的下一个列值。 自增计数器只放于主存中,而不是放在磁盘中。

 

InnoDB 使用下列规则初始化自增计数器。数据库启动后,当用户第一次向表 T 插入数据或运行 SHOW TABLE STATUS 来显示表 T 时,InnoDB 将执行

SELECT MAX(auto-inc-column) FROM T FOR UPDATE,
	
同时将所得值加 1 现填入字段并记录表的自增记录器。如果表是空的则将值赋为 1 。注意在这个初始化过程中将为表加一个读锁(a normal x-locking read),这个锁将一直持续到事务处理结束。

 

InnoDB 为一个新建立的表以同样的方式建立自增计数器。

如果为一个自增列特别指定值 0 ,那么 InnoDB 将视为未为该列指定值而将该列赋于新值。

在自增计数器初始化后,在自增列中插入一个明确指定的新值 ,并且该值大于当前计数值,那么计数器将被设置为新的指定值。如果用户没有明确为它指定一个值,InnoDB 将自增计 数器,并将新值赋于自增列。

当访问自增计数器时,InnoDB 将使用一个特殊的表级锁定( AUTO-INC lock ),锁将一直保持到当前 SQL 语句的运行结束,而不是线程的结束。特殊的锁释放策略被引入是为了改善向有自增字段的表中插入数据的并发性能。两个事务处理不能在同一张表上建立AUTO-INC 锁。

注意如果事务从自增计数器中取值,那么当事务回滚时在自增列顺序上可能会产生空隙。

如果给一个自增字段指定一个无效值或值比定义的整型类型最大值域还大,那么自增机制 的状态将无法预知。

4.5 InnoDB 和 MySQL 复制(replication)

MySQL 的复制特征(replication feature)在 InnoDB 表上的工作就与它在 MyISAM 表类型上一样。在 master 中的表类型与 slave 中的表类型是不一致时使用复制(replication)是可能的。举例来说,你可以将 master 中的一个 InnoDB 表的更改复制到一个 slave 中的 MyISAM 表中去。

为了设立一个 master 的新的 slave ,你必须建立一份 InnoDB 表空间和日志文件(log files)的复本,同样也包括 InnoDB 表相对应的 .frm 文件,并将这些复本移动到 slave 中。这看上去有点像下面 章节 7 关于移动一个 InnoDB 数据库的说明。如果你可以关闭 master,你可以建立一个 InnoDB 表空间和日志文件的冷备份(cold backup)来建立一个 slave。为了建立一个新的 slave 而不将 master 数据库关闭,你可以使用一个非免费的 InnoDB Hot Backup tool

在 InnoDB 复制中的一引起小限制:

最后,一个较短的解释有关 MySQL 在 master 中处理事务复制失败。MySQL 的复制是基于二进制日志(binlog)的,它用于 MySQL 记录修改了数据的 SQL 语句。slave 读取 master 的二进制日志(binlog),并运行同样的 SQL 语句。如果一个语句失败,举例来说,由于违背一个外键约束 ,那么这个语句将不记入二进制日志中,因而也不复制到 slave 中。如果一个事务回滚了,那么事务中的 SQL 语句将不记入二进制日志中,同样这个事务在 slave 中根本也不会运行。