MySQL Reference Manual for version 4.1.0-alpha.


6.4 数据操纵:SELECT, INSERT, UPDATE, DELETE

6.4.1 SELECT 句法

SELECT [STRAIGHT_JOIN]
       [SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
       [SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS] [HIGH_PRIORITY]
       [DISTINCT | DISTINCTROW | ALL]
    select_expression,...
    [INTO {OUTFILE | DUMPFILE} 'file_name' export_options]
    [FROM table_references
      [WHERE where_definition]
      [GROUP BY {unsigned_integer | col_name | formula} [ASC | DESC], ...]
      [HAVING where_definition]
      [ORDER BY {unsigned_integer | col_name | formula} [ASC | DESC] ,...]
      [LIMIT [offset,] rows | rows OFFSET offset]
      [PROCEDURE procedure_name(argument_list)]
      [FOR UPDATE | LOCK IN SHARE MODE]]

SELECT 用于检索从一个或多个表中选取出的行。select_expression 表示你希望检索的列。 SELECT 也可以被用于检索没有引用任何表的计算列。例如:

mysql> SELECT 1 + 1;
         -> 2

所有使用的关键词必须严格以上面所显示的次序被给出。举例来说,一个 HAVING 子句必须出现在 GROUP BY 子句后,在 ORDER BY 字句之前。

6.4.1.1 JOIN 句法

MySQL 支持在 SELECT 中使用下面所示的 JOIN 句法:

table_reference, table_reference
table_reference [CROSS] JOIN table_reference
table_reference INNER JOIN table_reference join_condition
table_reference STRAIGHT_JOIN table_reference
table_reference LEFT [OUTER] JOIN table_reference join_condition
table_reference LEFT [OUTER] JOIN table_reference
table_reference NATURAL [LEFT [OUTER]] JOIN table_reference
{ OJ table_reference LEFT OUTER JOIN table_reference ON conditional_expr }
table_reference RIGHT [OUTER] JOIN table_reference join_condition
table_reference RIGHT [OUTER] JOIN table_reference
table_reference NATURAL [RIGHT [OUTER]] JOIN table_reference

table_reference 定义如下:

table_name [[AS] alias] [[USE INDEX (key_list)] | [IGNORE INDEX (key_list)] | [FORCE INDEX (key_list)]]

join_condition 定义如下:

ON conditional_expr |
USING (column_list)

通常不应该在 ON 存在任何条件式,它是用于限制在结果集中有哪个行的(对于这个规则也有例外)。如果你希望哪个记录行应该在结果中,你必须在 WHERE 子句中限制它。

注意,在早于 3.23.17 的版本中,INNER JOIN 不接受一个 join_condition!

上面所显示的最后一个 LEFT OUTER JOIN 句法仅仅是为了与 ODBC 兼容而存在的:

一些例子:

mysql> SELECT * FROM table1,table2 WHERE table1.id=table2.id;
mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id;
mysql> SELECT * FROM table1 LEFT JOIN table2 USING (id);
mysql> SELECT * FROM table1 LEFT JOIN table2 ON table1.id=table2.id
    ->          LEFT JOIN table3 ON table2.id=table3.id;
mysql> SELECT * FROM table1 USE INDEX (key1,key2)
    ->          WHERE key1=1 AND key2=2 AND key3=3;
mysql> SELECT * FROM table1 IGNORE INDEX (key3)
    ->          WHERE key1=1 AND key2=2 AND key3=3;

查看章节 5.2.6 MySQL 如何优化 LEFT JOIN 和 RIGHT JOIN。

6.4.1.2 UNION 句法

SELECT ...
UNION [ALL]
SELECT ...
  [UNION
   SELECT ...]

UNION 在 MySQL 4.0.0 中被实现。

UNION 用于将多个 SELECT 语句的结果联合到一个结果集中。

在 SELECT 中的 select_expression 部分列出的列必须具有同样的类型。第一个 SELECT 查询中使用的列名将作为结果集的列名返回。

SELECT 命令是一个普通的选择命令,但是有下列的限制:

如果你不为 UNION 使用关键词 ALL,所有返回的记录行将是唯一的,就好像你为整个返回集使用了一个 DISTINCT。如果你指定了 ALL,那么你将得到从所有使用的 SELECT 语句中返回的所有匹配记录行。

如果你希望对整个 UNION 结果使用一个 ORDER BY,你应该使用圆括号:

(SELECT a FROM table_name WHERE a=10 AND B=1 ORDER BY a LIMIT 10)
UNION
(SELECT a FROM table_name WHERE a=11 AND B=2 ORDER BY a LIMIT 10)
ORDER BY a;

6.4.2 HANDLER 句法

HANDLER tbl_name OPEN [ AS alias ]
HANDLER tbl_name READ index_name { = | >= | <= | < } (value1,value2,...)
    [ WHERE ... ] [LIMIT ... ]
HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST }
    [ WHERE ... ] [LIMIT ... ]
HANDLER tbl_name READ { FIRST | NEXT }
    [ WHERE ... ] [LIMIT ... ]
HANDLER tbl_name CLOSE

HANDLER 语句提供了直接访问 MyISAM 表存储引擎的接口。

HANDLER 语句的第一个形式打开一个表,通过后来的 HANDLER ... READ 语句使它可读取。这个表对象将不能被其它线程共享,也不会被关闭,除非线程调用 HANDLER tbl_name CLOSE 或线程关闭。

第二个形式读取指定的索引遵从那个条件并且适合 WHERE 条件的一行(或更多的,由 LIMIT 子句指定)。 如果索引由几个部分组成(范围有几个列),值以逗号分隔的列表指定;如果只提供的一部分值,那么第一个列是必需的。

第三个形式从表中以索引的顺序读取匹配 WHERE 条件的一行(或更多的,由 LIMIT 子句指定)。

第四个形式(没有索引清单)从表中以自然的列顺序(在数据文件中存储的次序)读取匹配 WHERE 条件的一行(或更多的,由 LIMIT 子句指定)。如果期望做一个全表扫描,它将比 HANDLER tbl_name READ index_name 更快。

HANDLER ... CLOSE 关闭一个以 HANDLER ... OPEN 打开的表。

HANDLER 是一个稍微低级的语句。举例来说,它不提供一致性约束。更确切地说,HANDLER ... OPEN 不 接受一个表的快照,并且 不 锁定表。这就意味着在一个 HANDLER ... OPEN 被执行后,表数据仍会被 (这个或其它的线程) 修改,这些修改可能在 HANDLER ... NEXT 和 HANDLER ... PREV 扫描中才会部分地出现。

使用这个接口代替普通 SQL 的原因是:

6.4.3 INSERT 句法

    INSERT [LOW_PRIORITY | DELAYED] [IGNORE]
        [INTO] tbl_name [(col_name,...)]
        VALUES ((expression | DEFAULT),...),(...),...
        [ ON DUPLICATE KEY UPDATE col_name=expression, ... ]
or  INSERT [LOW_PRIORITY | DELAYED] [IGNORE]
        [INTO] tbl_name [(col_name,...)]
        SELECT ...
or  INSERT [LOW_PRIORITY | DELAYED] [IGNORE]
        [INTO] tbl_name
        SET col_name=(expression | DEFAULT), ...
        [ ON DUPLICATE KEY UPDATE col_name=expression, ... ]

INSERT 将新行插入到一个已存在的表中。INSERT ... VALUES 形式的语句基于明确的值插入记录行。INSERT ... SELECT 形式的语句从另一个或多个表中选取出值,并将其插入。有多重值列表的 INSERT ... VALUES 形式的语句在 MySQL 3.22.5 或更新的版本中被支持。col_name=expression 句法在 MySQL 3.22.10 或更新的版本中得到支持。

tbl_name 是记录将要被插入的表。列名列表或 SET 子句指出语句指定的值赋给哪个列:

如果你使用 INSERT ... SELECT 或一个 INSERT ... VALUES 语句插入多值列,你可以使用 C API 函数 mysql_info() 得到查询的信息。信息字串的格式如下:

Records: 100 Duplicates: 0 Warnings: 0

Duplicates 指出因与某些现有的唯一索引值重复而不能被插入的记录行数目。Warnings 指出在尝试插入的列值中在某些方面可能有问题的数目。在下列任何一个条件下,警告都会发生:

6.4.3.1 INSERT ... SELECT 句法

INSERT [LOW_PRIORITY] [IGNORE] [INTO] tbl_name [(column list)] SELECT ...

使用 INSERT ... SELECT 语句,你可以从一个或多个表中读取多个记录行,并将其快速地插入到一个表中。

INSERT INTO tblTemp2 (fldID) SELECT tblTemp1.fldOrder_ID FROM tblTemp1 WHERE
tblTemp1.fldOrder_ID > 100;

一个 INSERT ... SELECT 语句有下列条件的限止:

你当然也可以使用 REPLACE 代替 INSERT 来盖写老的记录行。

6.4.4 INSERT DELAYED 句法

INSERT DELAYED ...

INSERT 语句的 DELAYED 选项是一个 MySQL 特有的选项,如果你的客户端不能等待 INSERT 的完成,这将会是很有用的。This is a common problem when you use MySQL for logging and 当你打开日志记录使用 MySQL 并且你周期性的需花费很长时间才完成的 SELECT 和 UPDATE 语句时,这将是一个很普遍的问题。DELAYED 在 MySQL 3.22.15 中被引入。它是 MySQL 对 ANSI SQL92 的一个扩展。

INSERT DELAYED 仅仅工作与 ISAM 和 MyISAM 表。注意,因为 MyISAM 表支持并发的 SELECT 和 INSERT,如果在数据文件中没有空闲的块,那你将很少需要对 MyISAM 表使用 INSERT DELAYED。查看章节 7.1 MyISAM 表。

当你使用 INSERT DELAYED 时,客户端将立即得到一个 OK,当表不被任何其它线程使用时,该行将被插入。

使用 INSERT DELAYED 的另一个主要的好处就是,从很多客户端来的插入请求会被打包在一起并写入一个块中。这比做许多单独的插入要快的多。

注意,当前的记录行队列是被存储在内存中的,一直到他们被插入到表中。这就意味着,如果你使用强制的方法(kill -9) 杀死 mysqld,或者如果意外地死掉,任何没有写到磁盘中的记录行队列都将会丢失!

下面详细地描述当你为 INSERT 或 REPLACE 使用 DELAYED 选项时会发生什么。在这个描述中,“线程”是遇到一个 INSERT DELAYED 命令的线程,“处理器”是处理所有对于一个特定表的 INSERT DELAYED 语句的线程。

注意,如果表没有在使用中,INSERT DELAYED 将比一个正常的 INSERT 慢。让服务器为你使用 INSERT DELAYED 的每张表处理一个单独的线程,也是有额外的开销的。这就意味着,你应该在确定你的确需要它时才使用 INSERT DELAYED。

6.4.5 UPDATE 句法

UPDATE [LOW_PRIORITY] [IGNORE] tbl_name
    SET col_name1=expr1 [, col_name2=expr2 ...]
    [WHERE where_definition]
    [ORDER BY ...]
    [LIMIT rows]

or

UPDATE [LOW_PRIORITY] [IGNORE] tbl_name [, tbl_name ...]
    SET col_name1=expr1 [, col_name2=expr2 ...]
    [WHERE where_definition]

UPDATE 以新的值更新现存表中行的列。SET 子句指出要修改哪个列和他们应该给定的值。WHERE 子句如果被给出,指定哪个记录行应该被更新。否则,所有的记录行被更新。如果 ORDER BY 子句被指定,记录行将被以指定的次序更新。

如果你指定关键词 LOW_PRIORITY,UPDATE 的执行将被延迟,直到没有其它的客户端正在读取表。

如果你指定关键词 IGNORE,该更新语句将不会异常中止,即使在更新过程中出现重复键错误。导致冲突的记录行将不会被更新。

如果在一个表达式中从 tbl_name 中访问一个列,UPDATE 使用列的当前值。举例来说,下面的语句设置 age 列值为它的当前值加 1 :

mysql> UPDATE persondata SET age=age+1;

UPDATE 赋值是从左到右计算的。举例来说,下列语句将 age 列设置为它的两倍,然后再加 1 :

mysql> UPDATE persondata SET age=age*2, age=age+1;

如果你设置列为其当前的值,MySQL 注意到这点,并不更新它。

UPDATE 返回实际被改变的记录行数目。在 MySQL 3.22 或更新的版本中,C API 函数 mysql_info() 返回被匹配并更新的记录行数目,以及在 UPDATE 期间发生的警告的数目。

在 MySQL 3.23 中,你可以使用 LIMIT # 来确保只有给定的记录行数目被更改。

如果一个 ORDER BY 子句被使用(从 MySQL 4.0.0 开始支持),记录行将以指定的次序被更新。这实际上只有连同 LIMIT 一起才有用。

从 MySQL 4.0.4 开始,你也可以执行一个包含多个表的 UPDATE 的操作:

UPDATE items,month SET items.price=month.price
WHERE items.id=month.id;

注意:多表 UPDATE 不可以使用 ORDER BY 或 LIMIT。

6.4.6 DELETE 句法

DELETE [LOW_PRIORITY] [QUICK] FROM table_name
       [WHERE where_definition]
       [ORDER BY ...]
       [LIMIT rows]

or

DELETE [LOW_PRIORITY] [QUICK] table_name[.*] [, table_name[.*] ...]
       FROM table-references
       [WHERE where_definition]

or

DELETE [LOW_PRIORITY] [QUICK]
       FROM table_name[.*] [, table_name[.*] ...]
       USING table-references
       [WHERE where_definition]

DELETE 从 table_name 中删除 where_definition 中给定条件的记录行,并返回删除的记录数目。

如果你发出一个没有 WHERE 子句的 DELETE,所有的记录行将被删除。如果你以 AUTOCOMMIT 模式执行它,那么它类似于 TRUNCATE。查看章节 6.4.7 TRUNCATE 句法。在 MySQL 3.23 中,没有一个 WHERE 子句的 DELETE 将返回零作为受影响的记录数目。

当你删除所有记录行时,如果你真的希望知道多少条记录被删除,你可以使用一个这种形式的 DELETE 语句:

mysql> DELETE FROM table_name WHERE 1>0;

注意,这将比一个没有 WHERE 子句的 DELETE FROM table_name 语句慢,因为它一次只删除一行。

如果你指定关键词 LOW_PRIORITY,DELETE 的执行将被延迟,直到没有其它的客户端正在读取表。

如果你指定关键词 QUICK,那么在删除过程中存储引擎将不会归并索引叶,这可能会加速某些类型的删除操作。

在 MyISAM 表中,删除了的记录被放在一个链接表中维护,以后的 INSERT 操作将重新使用删除后的记录位置。为了回收闲置的空间,并减小文件尺寸,使用 OPTIMIZE TABLE 语句或 myisamchk 实用程序重新组织表。OPTIMIZE TABLE 使用比较容易,但是 myisamchk 更快点。查看章节 4.5.1 OPTIMIZE TABLE 句法 和章节 4.4.6.10 表优化。

第一个多表删除格式从 MySQL 4.0.0 开始被支持。第二个多表删除格式从 MySQL 4.0.2 开始被支持。

仅仅在 FROM 或 USING 子句 之前 列出的表中的匹配记录行被删除。效果就是,你要以从多个表中同时删除记录行,并且同样可以有其它的表用于检索。

在表名后的 .* 仅仅是为了兼容 Access:

DELETE t1,t2 FROM t1,t2,t3 WHERE t1.id=t2.id AND t2.id=t3.id

or

DELETE FROM t1,t2 USING t1,t2,t3 WHERE t1.id=t2.id AND t2.id=t3.id

在上面的情况下,我们仅仅从 t1 和 t2 表中删除匹配的记录行。

如果一个 ORDER BY 子句被使用(从 MySQL 4.0.0 开始支持), 记录行将以指定的次序删除。这实际上只有连同 LIMIT 一起才有用。示例如下:

DELETE FROM somelog
WHERE user = 'jcole'
ORDER BY timestamp
LIMIT 1

这将删除匹配 WHERE 子句的,并且最早被插入(通过 timestamp 来确定)的记录行。

DELETE 语句的LIMIT rows 选项是 MySQL 特有的,它告诉服务器在控制权被返回到客户端之前可被删除的最大记录行数目。这可以用来确保一个特定的 DELETE 命令不会占用太长的时间。你可以简单地重复使用 DELETE 命令,直到被影响的记录行数目小于 LIMIT 值。

从 MySQL 4.0 开始,在 DELETE 语句中可以指定多个表,用以从一个表中删除依赖于多表中的特殊情况的记录行。然而,在一个多表删除中,不能使用 ORDER BY 或 LIMIT。

6.4.7 TRUNCATE 句法

TRUNCATE TABLE table_name

在 3.23 中,TRUNCATE TABLE 被映射为 COMMIT ; DELETE FROM table_name。查看章节 6.4.6 DELETE 句法。

在下面的方式中,TRUNCATE TABLE 不同于 DELETE FROM ...:

TRUNCATE 是一个 Oracle SQL 的扩展。

6.4.8 REPLACE句法

    REPLACE [LOW_PRIORITY | DELAYED]
        [INTO] tbl_name [(col_name,...)]
        VALUES (expression,...),(...),...
or  REPLACE [LOW_PRIORITY | DELAYED]
        [INTO] tbl_name [(col_name,...)]
        SELECT ...
or  REPLACE [LOW_PRIORITY | DELAYED]
        [INTO] tbl_name
        SET col_name=expression, col_name=expression,...

REPLACE 功能与 INSERT 完全一样,除了如果在表中存在一个老的记录与新记录在一个 UNIQUE 或 PRIMARY KEY 上有相同的值,那么在新记录被插入之前,老的记录将被删除。查看章节 6.4.3 INSERT 句法。

换句话说,你不可以从一个 REPLACE 中访问老的记录行的值。某些老的 MySQL 版本中,你或许可以这样做,但是这是一个 Bug,现在已被修正了。

为了能够使用 REPLACE,你必须有对该表的 INSERT 和 DELETE 权限。

当你使用一个 REPLACE 时,如果新的记录行代替了老的记录行,mysql_affected_rows() 将返回 2。这是因为在新行被插入之前,重复记录行被先删除了。

这个事实使得判断 REPLACE 是否是添加一条记录还是替换一条记录很容易:检查受影响记录行的值是 1 (添加)还是 2(替换)。

注意,除非你使用一个 UNIQUE 索引或 PRIMARY KEY ,使用 REPLACE 命令是没有感觉的,因为它会仅仅执行一个 INSERT。

6.4.9 LOAD DATA INFILE 句法

LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name.txt'
    [REPLACE | IGNORE]
    INTO TABLE tbl_name
    [FIELDS
        [TERMINATED BY '\t']
        [[OPTIONALLY] ENCLOSED BY '']
        [ESCAPED BY '\\' ]
    ]
    [LINES TERMINATED BY '\n']
    [IGNORE number LINES]
    [(col_name,...)]

LOAD DATA INFILE 语句以非常高的速度从一个文本文件中读取记录行并插入到一个表中。如果 LOCAL 关键词被指定,文件从客户端主机读取。如果 LOCAL 没有被指定,文件必须位于服务器上。(LOCAL 在 MySQL 3.22.6 或更新的版本中被支持。)

由于安全性的原因,当读取位于服务器端的文本文件时,文件必须处于数据库目录或可被所有人读取的地方。同时,为了对服务器端的文件使用 LOAD DATA INFILE,你必须在服务器主机上有 FILE 权限。查看章节 4.2.7 由 MySQL 提供的权限。

在 MySQL 3.23.49 和 MySQL 4.0.2 中,只有当你没有以 --local-infile=0 选项启动 mysqld,或你没有禁止你的客户端程序支持 LOCAL的情况下,LOCAL 才会工作。查看章节 4.2.4 LOAD DATA LOCAL 的安全性问题.

如果你指定关键词 LOW_PRIORITY,LOAD DATA 语句的执行将会被延迟,直到没有其它的客户端正在读取表。

如果你对一个 MyISAM 表指定关键词 CONCURRENT,那么当 LOAD DATA正在执行时,其它的线程仍可以从表中检索数据。使用这个选项时,如果同时也有其它的线程正在使用表,这当然会有一点影响 LOAD DATA 的执行性能。

使用 LOCAL 将比让服务器直接访问文件要慢一些,因为文件的内容必须从客户端主机传送到服务器主机。而在另一方面,你不再需要有 FILE 权限用于装载本地文件。

如果你使用先于 MySQL 3.23.24 的版本,你不能够以 LOAD DATA INFILE 读取一个 FIFO 。如果你需要从一个 FIFO (例如,gunzip 的输出文件) 中读取,可以使用 LOAD DATA LOCAL INFILE 代替。

你也可以使用 mysqlimport 实用程序装载数据文件;它通过发送一个 LOAD DATA INFILE 命令到服务器来动作。--local 选项使得 mysqlimport 从客户端主机读取数据文件。如果客户端与服务器支持压缩协议,你可以指定 --compress 选项,以在较慢的网络中获得更好的性能。

当从服务器主机定位文件时,服务器使用下列规则:

注意,这些规则意味着,一个以 `./myfile.txt' 给出的文件是从服务器的数据目录中读取的,然而,以 `myfile.txt' 给出的一个文件是从当前数据库的数据目录下读取的。举例来说,下面的 LOAD DATA 语句从 db1 数据库目录下读取文件 `data.txt',因为 db1 是当前数据库,即使该语句明确地指定读取的文件被放入到 db2 数据库中的一个表中:

mysql> USE db1;
mysql> LOAD DATA INFILE "data.txt" INTO TABLE db2.my_table;

REPLACE 和 IGNORE 关键词控制对与现有的记录在唯一键值上重复的记录的处理。如果你指定 REPLACE,新的记录行将替换有相同唯一键值的现有记录行。如果你指定 IGNORE,将跳过与现有的记录行在唯一键值上重复的输入记录行。如果你没有指定任何一个选项,当重复键值出现时,将会发生一个错误,文本文件的剩余部分也将被忽略。

如果你使用 LOCAL 关键词从一个本地文件中读取数据,在此操作过程中,服务器没有办法停止文件的传送,因此缺省的处理方式就好像是 IGNORE 被指定一样。

如果你在一个空的 MyISAM 表上使用 LOAD DATA INFILE,所有非唯一索引会以一个分批方式被创建(就像 REPAIR)。当有许多索引时,这通常可以使 LOAD DATA INFILE 更快一些。

LOAD DATA INFILE 的 SELECT ... INTO OUTFILE 的逆操作。查看章节 6.4.1 SELECT 句法。 使用 SELECT ... INTO OUTFILE 将数据从一个数据库写到一个文件中。使用 LOAD DATA INFILE 读取文件到数据库中。两个命令的 FIELDS 和 LINES 子句的句法是一样的。两个子句都是可选的,但是如果两个同时被指定,FIELDS 子句必须出现在 LINES 子句之前。

如果你指定一个 FIELDS 子句,它的子句 (TERMINATED BY、[OPTIONALLY] ENCLOSED BY 和 ESCAPED BY) 也是可选的,不过,你必须至少指定它们中的一个。

如果你没有指定一个 FIELDS 子句,缺省的相同于如果你这样写:

FIELDS TERMINATED BY '\t' ENCLOSED BY '' ESCAPED BY '\\'

如果你没有指定一个 LINES 子句,缺省的相同于如果你这样写:

LINES TERMINATED BY '\n'

换句话说,当读取输入时,缺省值导致 LOAD DATA INFILE 表现如下:

相反的,当写入输出时,缺省值导致 SELECT ... INTO OUTFILE 表现如下:

注意,为了写 FIELDS ESCAPED BY '\\',你必须指定两个反斜线,该值会作为一个反斜线被读入。

IGNORE number LINES 选项可被用于忽略文件开头处的一个列名的头:

mysql> LOAD DATA INFILE "/tmp/file_name" INTO TABLE test IGNORE 1 LINES;

当你一前一后地使用 SELECT ... INTO OUTFILE 和 LOAD DATA INFILE 将数据从一个数据库写到一个文件中,然后再从文件中将它读入数据库中时,两个命令的字段和行处理选项必须匹配。否则,LOAD DATA INFILE 将不能正确地解释文件内容。假设你使用 SELECT ... INTO OUTFILE 以逗号分隔字段的方式将数据写入到一个文件中:

mysql> SELECT * INTO OUTFILE 'data.txt'
    ->          FIELDS TERMINATED BY ','
    ->          FROM ...;

为了将由逗号分隔的文件读回时,正确的语句应该是:

mysql> LOAD DATA INFILE 'data.txt' INTO TABLE table2
    ->           FIELDS TERMINATED BY ',';

如果你试图用下面所示的语句读取文件,它将不会工作,因为命令 LOAD DATA INFILE 以定位符区分字段值:

mysql> LOAD DATA INFILE 'data.txt' INTO TABLE table2
    ->           FIELDS TERMINATED BY '\t';

可能的结果是每个输入行将被解释为一个单独的字段。

LOAD DATA INFILE 也可以被用来读取从外部来源获得的文件。例如,dBASE 格式的文件,字段以逗号分隔并以双引号包围着。如果文件中的行以一个换行符终止,那么下面所示的可以说明你将用来装载文件的字段和行处理选项:

mysql> LOAD DATA INFILE 'data.txt' INTO TABLE tbl_name
    ->           FIELDS TERMINATED BY ',' ENCLOSED BY '"'
    ->           LINES TERMINATED BY '\n';

任何字段和行处理选项都可以指定一个空字符串('')。如果不是空的,FIELDS [OPTIONALLY] ENCLOSED BY 和 FIELDS ESCAPED BY 值必须是一个单个字符。FIELDS TERMINATED BY 和 LINES TERMINATED BY 值可以超过一个字符。例如,为了写入由回车换行符终止的行,或读取包含这样的行的文件,应该指定一个 LINES TERMINATED BY '\r\n' 子句。

举例来说,为了读取一个文件到一个 SQL 表中,文件以一行 %% 分隔(开玩笑的),你可以这样做:

CREATE TABLE jokes (a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, joke TEXT
NOT NULL);
LOAD DATA INFILE "/tmp/jokes.txt" INTO TABLE jokes FIELDS TERMINATED BY ""
LINES TERMINATED BY "\n%%\n" (joke);

FIELDS [OPTIONALLY] ENCLOSED BY 控制字段的包围字符。对于输出 (SELECT ... INTO OUTFILE),如果你省略单词 OPTIONALLY,所有的字段被 ENCLOSED BY 字符包围。这样的一个输出文件(以一个逗号作为字段分界符)示例如下:

"1","a string","100.20"
"2","a string containing a , comma","102.20"
"3","a string containing a \" quote","102.20"
"4","a string containing a \", quote and comma","102.20"

如果你指定 OPTIONALLY,ENCLOSED BY 字符仅被作用于包围 CHAR 和 VARCHAR 字段:

1,"a string",100.20
2,"a string containing a , comma",102.20
3,"a string containing a \" quote",102.20
4,"a string containing a \", quote and comma",102.20

注意,在一个字段值中出现的 ENCLOSED BY 字符,通过用 ESCAPED BY 字符作为其前缀对其转义。同时也要注意,如果你指定一个空的 ESCAPED BY 值,可能会产生不能被 LOAD DATA INFILE 正确读出的输出文件。例如,如果转义字符为空,上面显示的输出将变成如下显示的输出。请注意第四行的第二个字段,它包含一个逗号跟在一个引号后的两个字符,这(错误的)看起来像是一个字段的终止:

1,"a string",100.20
2,"a string containing a , comma",102.20
3,"a string containing a " quote",102.20
4,"a string containing a ", quote and comma",102.20

对于输入,ENCLOSED BY 字符如果存在,它将从字段值的尾部被剥离。(不管 OPTIONALLY 是否被指定,都是这样;对于输入解释,OPTIONALLY 不会影响它。) 由ESCAPED BY 字符领先于 ENCLOSED BY 字符的出现,将被解释为当前字段值的一部分。另外,在字段中出现的重复的 ENCLOSED BY 字符被解释为单个 ENCLOSED BY ,只要字段本身也是以该字符开始的。例如,如果 ENCLOSED BY '"' 被指定,引号将做如下处理:

"The ""BIG"" boss"  -> The "BIG" boss
The "BIG" boss      -> The "BIG" boss
The ""BIG"" boss    -> The ""BIG"" boss

FIELDS ESCAPED BY 控制如何写入或读出特殊字符。如果 FIELDS ESCAPED BY 字符不是空的,它将被用于做为下列输出字符的前缀:

如果 FIELDS ESCAPED BY 字符为空,没有字符被转义。指定一个空的转义字符可能不是一个好的主意,特别是如果你的数据字段值中包含刚才列表中的任何字符时。

对于输入,如果 FIELDS ESCAPED BY 字符不为空,该字符的出现将会被剥离,后续的字符在字面上做为字段值的一部分。除了一个转义的 “0” 或 “N” (即,\0 或\N,如果转义字符为 `\')。这些序列被解释为 ASCII 0 (一个零值字节) 和 NULL。查看下面的有关 NULL 处理的规则。

关于更多的 “\” 转义句法信息,查看章节 6.1.1 文字:怎么写字符串与数字。

在某些情况下,字段与行处理相互作用:

NULL 值的处理有很多,取决于你所使用的 FIELDS 和 LINES 选项:

一些不能被 LOAD DATA INFILE 支持的情况:

下面的例子将装载 persondata 表的所有列:

mysql> LOAD DATA INFILE 'persondata.txt' INTO TABLE persondata;

没有字段列被指定,因而 LOAD DATA INFILE 认为输入行包含表列中所有的字段。使用缺省的 FIELDS 和 LINES 值。

如果你希望装载表中的某些列,那指定一个字段列表:

mysql> LOAD DATA INFILE 'persondata.txt'
    ->           INTO TABLE persondata (col1,col2,...);

如果输入文件的字段次序不同于表中列的顺序,你也必须指定一个字段列表。否则 MySQL 不知道如何将输入字段与表中的列匹配。

如果一个行有很少的字段,没有输入字段的列将被设置为缺省值。缺省值赋值在章节 6.5.3 CREATE TABLE 句法 中被描述。

一个空的字段值不同于字段值丢失的解释:

注意,如果在一个 INSERT 或 UPDATE 语句中明确地将一个空字符串赋给一个字符串、数字或日期或时间类型,你会得到与上面相同的结果。

如果对 TIMESTAMP 列指定一个 NULL 值,或者当字段列表被指定时, TIMESTAMP 在字段列表中被遗漏(仅仅第一个 TIMESTAMP 列被影响),TIMESTAMP 列会被设置为当前的日期和时间。

如果输入的记录行有太多的字段,多余的字段将被忽略,并增加警告的数目。

LOAD DATA INFILE 认为所有的输入均是字符串,因而,对于 ENUM 或 SET 列,你不能以 INSERT 语句的形式为其设置数字值。所有的 ENUM 和 SET 必须以字符串指定!

如果你正在使用 C API,当 LOAD DATA INFILE 查询结束时,你可以调用 API 函数 mysql_info() 获得有关查询的信息。信息串的格式如下:

Records: 1  Deleted: 0  Skipped: 0  Warnings: 0

警告会在某些情况下发生,这些情况与值通过 INSERT 语句插入时发生警告的情况一样 (查看章节 6.4.3 INSERT 句法),但是 LOAD DATA INFILE 有一点与它不一样,当在输入行中有太多或过少的字段,它也会产生警告。警告不会被存储在任何地主;警告的数目仅能表示一切是否顺利。如果得到警告,并希望确切地知道为什么会得到它们,一个方法就是使用 SELECT ... INTO OUTFILE,将它保存到另外一个文件中,并与原先的输入文件进行比较。

如果你需要 LOAD DATA 从一个管道中读取,你可以使用下面的技巧:

mkfifo /mysql/db/x/x
chmod 666 /mysql/db/x/x
cat < /dev/tcp/10.1.1.12/4711 > /nt/mysql/db/x/x
mysql -e "LOAD DATA INFILE 'x' INTO TABLE x" x

如果你使用的版本早于 MySQL 3.23.25,你只能通过 LOAD DATA LOCAL INFILE 来执行上面。

有关 INSERT 相对 LOAD DATA INFILE 的效率和加快 LOAD DATA INFILE 的更多信息,请查看章节 5.2.9 INSERT 查询的速度。

6.4.10 DO 句法

DO expression, [expression, ...]

执行表达式,但不返回任何结果。这是 SELECT expression, expression 的一个缩写,但是当你并不关心结果时,它稍有点优势,因为它稍稍快一点。

这主要有益于有副作用的函数,比如 RELEASE_LOCK。