译者:晏子 (clyan@sohu.com) 主页:http://linuxdb.yeah.net
MySQL包含了一些可能在其他SQL数据库找不到的扩充。要注意如果你使用他们,你的代码将不与其他SQL服务器兼容。在一些情况下,你可以编写包括MySQL扩展的代码,但是仍然是可移植的,通过使用/*!
... */
形式的注释。在这种情况下,MySQL将进行词法分析并且执行在注释内的代码,好像它是任何其它MySQL语句,但是其他SQL服务器将忽略扩展。例如:
SELECT /*! STRAIGHT_JOIN */ col_name FROM table1,table2 WHERE ...
如果你在'!'后增加一个版本数字,该语法将仅在MySQL版本是等于或比使用的版本数字新时才执行:
CREATE /*!32302 TEMPORARY */ TABLE (a int);
上面的意思是如果你有3.23.02或更新,那么MySQL将使用TEMPORARY
关键词。
MySQL扩展被列在下面:
MEDIUMINT
、SET
、ENUM
和不同的BLOB
和TEXT
类型。
AUTO_INCREMENT
、BINARY
、UNSIGNED
和ZEROFILL
。
BINARY
属性或使用BINARY
强制符声明列,它导致根据MySQL服务器主机的ASCII顺序进行排序。
db_name.tbl_name
语法访问不同数据库中的表。一些SQL服务器提供同样的功能但是称它们为这User
space
(用户空间)。MySQL不支持类似在create
table ralph.my_table...IN my_tablespace
中的表空间。 LIKE
在数字列上被允许。 SELECT
语句里面使用INTO OUTFILE
和STRAIGHT_JOIN
。见7.12 SELECT
句法. SELECT
语句中SQL_SMALL_RESULT
选项。 EXPLAIN SELECT
得到如何联结表的描述。 CREATE TABLE
语句里面使用索引、在字段前缀上的索引和使用INDEX
或KEY
。见7.7 CREATE TABLE
句法。 CREATE TABLE
使用TEMPORARY
或IF NOT EXISTS
。 COUNT(DISTINCT list)
,这里“list”超过一个元素。 ALTER TABLE
语句里面使用CHANGE col_name
、DROP
col_name
或DROP INDEX
。见7.8
ALTER TABLE
句法。 ALTER TABLE
里面语句使用IGNORE
。 ALTER TABLE
语句中使用多重ADD
、ALTER
、DROP
或CHANGE
子句。
IF EXISTS
的DROP TABLE
。DROP TABLE
语句抛弃多个表。 DELETE
语句的LIMIT
子句。 INSERT
和REPLACE
语句的DELAYED
子句。 INSERT
, REPLACE
, DELETE
和UPDATE
语句的LOW_PRIORITY
子句。
LOAD DATA INFILE
。在多数情况下,这句法与Oracle的LOAD
DATA INFILE
兼容。见7.16 LOAD
DATA INFILE
句法。 OPTIMIZE TABLE
语句。见7.9
OPTIMIZE TABLE
句法。 SHOW
语句。见7.21 SHOW
句法(得到表、列等的信息)。
SET OPTION
语句。见7.25
SET OPTION
句法。 GROUP BY
部分的被选择的列。这为一些很特定的情况给出更好的性能,而不是一般的查询。见7.4.13 用于GROUP BY
子句的函数。
||
和&&
意味着逻辑的OR和AND,就像在C程序语言中。在MySQL中,||
和OR
是同义词,&&
和AND
是同义词。正因为这个好的句法,MySQL对字符串并置的不支持ANSI
SQL ||
操作符;相反使用CONCAT()
,因为CONCAT()
接受任何数量的参数,很容易把||
操作符使用变换到MySQL。CREATE DATABASE
或DROP DATABASE
。见7.5 CREATE DATABASE
句法。
%
操作符是MOD()
一个同义词,即,N % M
等价于MOD(N,M)
。%
支持C程序员并与PostgreSQL兼容。
=
, <>
, <=
,<
, >=
,>
,
<<
, >>
, <=>
, AND
, OR
或LIKE
操作符可以放在SELECT
语句的FROM
左边用于比较列。例如:
mysql> SELECT col1=1 AND col2=2 FROM tbl_name;
LAST_INSERT_ID()
函数。见20.4.29
mysql_insert_id()
。 REGEXP
和NOT REGEXP
。 CONCAT()
或CHAR()
有一个参数或超过2个参数。(在MySQL中,这些函数可取任何数量的参数。)BIT_COUNT()
, CASE
, ELT()
, FROM_DAYS()
,
FORMAT()
, IF()
, PASSWORD()
, ENCRYPT()
,
md5()
, ENCODE()
, DECODE()
, PERIOD_ADD()
,
PERIOD_DIFF()
, TO_DAYS()
,或WEEKDAY()
函数。 TRIM()
整修子串。ANSI SQL 只支持单个字符的删除。 GROUP BY
函数STD()
, BIT_OR()
和BIT_AND()
。
REPLACE
而不是DELETE
+INSERT
。见7.15 REPLACE
句法。 FLUSH flush_option
语句。 :=
设置变量的可能性: SELECT @a:=SUM(total),@b=COUNT(*),@a/@b AS avg FROM test_table; SELECT @t1:=(@t2:=1)+@t3:=4,@t1,@t2,@t3;
如果你用--ansi
选项启动mysqld,MySQL的下列行为改变。
||
是字符串并置而不是OR
。"
将是一个标识符引号字符(象MySQL `
引号字符一样)而不是一个字符串引号字符。
REAL
将是FLOAT
一个同义词,不是DOUBLE
一个同义词。
我们尝试使得MySQL遵照ANSI SQL标准和ODBC SQL标准,但是在一些情况下,MySQL做一些不同的事情:
--
只是一个注释,如果后面跟一个白空字符。见5.4.7 `--'作为一个注释的开始。
VARCHAR
列,当值被存储时,拖后的空格被删除。见E MySQL已知的错误和设计缺限。 CHAR
列偷偷地被改变为VARCHAR
列。见7.7.1 平静的列指定变化。 REVOKE
来废除对一个表的权限。见7.26 GRANT
和REVOKE
句法。
下列功能在当前的MySQL版本是没有的。对于一张优先级表指出何时新扩展可以加入MySQL, 你应该咨询在线MySQL TODO 表。这是本手册最新的TODO表版本。见F 我们想要在未来加入到MySQL的事情列表(TODO)。
在MySQL中下列语句还不能工作:
SELECT * FROM table1 WHERE id IN (SELECT id FROM table2); SELECT * FROM table1 WHERE id NOT IN (SELECT id FROM table2);
然而,在很多情况下,你可以重写查询,而不用子选择:
SELECT table1.* FROM table1,table2 WHERE table1.id=table2.id; SELECT table1.* FROM table1 LEFT JOIN table2 ON table1.id=table2.id where table2.id IS NULL
对于更复杂的子查询,通常你可以创建临时的表保存子查询。然而在一些情况下,这种选择将行不通。最经常遇到的情形是DELETE
语句,对于它标准SQL不支持联结(join)(除了在子选择)。对于这种情况,有2个可用选择,直到子选择被MySQL支持。
第一个选择是使用一种过程化的程序语言(例如Perl或PHP)来提交一个SELECT
查询获得要被删除记录主键,并然后使用这些值构造DELETE
语句(DELETE
FROM ... WHERE ... IN (key1, key2, ...)
)。
第二个选择是使用交互式SQL自动构造一套DELETE
语句,使用MySQL扩展CONCAT()
(代替标准||
操作符)。例如:
SELECT CONCAT('DELETE FROM tab1 WHERE pkid = ', tab1.pkid, ';') FROM tab1, tab2 WHERE tab1.col1 = tab2.col2;
你可以把这个查询放在一个脚本文件并且从它重定向输入到mysql
命令行解释器,将其输出作为管道返回给解释器的第2个实例:
prompt> mysql --skip-column-names mydb < myscript.sql | mysql mydb
MySQL仅支持INSERT ... SELECT ...
和REPLACE ...
SELECT ...
,独立的子选择将可能在3.24.0得到,然而,在其他环境下,你现在可以使用函数IN()
。
SELECT INTO TABLE
MySQL还不支持Oracle SQL的扩展:SELECT ... INTO TABLE ...
.,相反MySQL支持ANSI
SQL句法INSERT INTO ... SELECT ...
,基本上他们是一样的。
另外,你可使用SELECT INTO OUTFILE...
或CREATE TABLE ...
SELECT
解决你的问题。
不支持事务处理。MySQL将在短时间内支持原子(atomic)操作,它象没有回卷的事务。用原子操作,你能执行一组INSERT
/SELECT
/whatever
命令并且保证没有其他线程介入。在本文中,你通常不会需要回卷。目前,你可通过使用LOCK
TABLES
和UNLOCK TABLES
命令阻止其他线程的干扰。见7.24 LOCK TABLES/UNLOCK TABLES
句法。
一个存储过程是能在服务器中编译并存储的一套SQL命令。一旦这样做了,顾客不需要一直重新发出全部查询,而可以参考存储过程。因为查询仅需一次词法分析并且较少的信息需要在服务器和客户之间传送,因此这提供了更好的性能。你与可以通过拥有在服务器中的函数库提升概念上的层次。
一个触发器是当一个特别的事件发生时,被调用的一个存储过程。例如,你可以安装一个存储过程,它在每次从一个交易表删除一条记录时触发,并且当它所有交易被删除时,自动地从一个客户表中删除相应的客户。
计划修改的语言将能处理存储过程,但是没有触发器。触发器通常使每件事情变慢,即使对他们不需要的查询。
为了俩解什么时候MySQL可能得到存储过程,见F 我们想在未来加入到MySQL的事情列表(TODO)。
注意,在SQL中外键不用于联结表,而主要用于检查参考完整性(RI)。如果你想要得到用一个SELECT
语句从多个表得到结果,
你通过联结表做!
SELECT * from table1,table2 where table1.id = table2.id;
在MySQL里存在FOREIGN KEY
句法仅仅为了与其他SQL供应商的CREATE
TABLE
命令相兼容;它不做任何事情。没有ON DELETE ...
的FOREIGN
KEY
句法主要用于文档目的。一些ODBC应用程序可以使用它自动生成WHERE
子句,但是这通常很容易的覆盖。
FOREIGN KEY
有时用作一个约束检查,但是如果行以正确的顺序被插入表,该检查实际上是不必要的。MySQL仅仅支持这些子句(不考虑是否他们工作!),因为一些应用程序要求他们存在。
在MySQL中,你可以解决ON DELETE ...
没被实现的问题,,在你从一个用外键的表删除记录时,通过为一个应用程序增加适当的DELETE
语句即可。实际上,这很快(在一些情况下更快)并且比使用外键更比便于移植。
在不久的将来我们将扩充FOREIGN KEY
实现,以便至少信息将在表说明文件中保存并且可以由mysqldump
和ODBC检索。
有很多与FOREIGN KEY
有关的问题我们不知道从哪儿开始:
INSERT
和UPDATE
语句是可怕的,并且在这种情况下几乎所有的FOREIGN
KEY
检查都是无用的,因为不管怎样你通常以正确的顺序在正确的表中插入记录。
FOREIGN KEY
唯一好的方面是它给ODBC和一些其他客户程序检查一张表如何被连接的能力,并且使用它们显示出连接图表并帮助构造应用。
MySQL不久将存储FOREIGN KEY
定义以便一个客户能询问并收到原来的连接如何进行的一个答案。当前的“.frm
文件格式没有它应有的地位。
MySQL不支持视图,但是它在TODO上。
有些其他SQL数据库使用'--'开始注释。MySQL有“#”作为开始数注释的字符,即使mysql
命令行工具删除以'--'开始的所有行。你也可以在MySQL中使用C注释风格/*
this is a comment */
。见7.29
注释句法。
MySQL3.23.3和以上版本支持'--'注释风格,只要注释跟在一个空格之后。这是因为这种退化的注释风格已经引起用像下列代码那样的自动生成的SQL查询的许多问题,这里我们自动地为!payment!插入支付值:
UPDATE tbl_name SET credit=credit-!payment!
你想出当payment
的值是负的时将发生什么吗?
因为1--1
在SQL中是合法的,我们认为'--'开始注释是可怕的。
然而在MySQL 3.23中,你可使用:1-- This is a comment
如果你正在运行一个比3.23早的MySQL的版本,下列的讨论才涉及你:
如果你在一个文本文件中有一个SQL程序,它包含'--'注释,你应该使用:
shell> replace " --" " #" < text-file-with-funny-comments.sql \ | mysql database 而不是通常:
shell> mysql database < text-file-with-funny-comments.sql
你也可以“现场”编辑命令文件将'--'注释改为'#'注释:
shell> replace " --" " #" -- text-file-with-funny-comments.sql
用这个命令改回他们:
shell> replace " #" " --" -- text-file-with-funny-comments.sql
Entry level SQL92。ODBC level 0-2。
COMMIT
/ROLLBACK
MySQL不支持COMMIT
-ROLLBACK。
问题是有效地处理COMMIT
-ROLLBACK
将需要完全不同于MySQL今天使用的表布局。MySQL也将需要额外的线程在表上做自动清除工作,而且磁盘用量将更高。这将使MySQL比现今慢上大约2-4倍。MySQL比几乎所有其他SQL数据库都快(一般至少快2-3倍)。原因之一就是缺少COMMIT
-ROLLBACK
。
目前,我们是更多地实现SQL服务器语言(象存储过程),有了它,你将确实很少需要COMMIT
-ROLLBACK,
这也将得到更好的性能。
通常需要事务的循环可以借助LOCK TABLES
进行编码,并且当你能即时地更新记录时,你不需要光标(cursor)。
我们在TODO上有事务和光标,然而并非相当优先。如果我们实现这些,将作为CREATE
TABLE
的选项,那意味着COMMIT
-ROLLBACK
将仅工作在那些表上,以便速度损失仅仅强加在那些表上。
我们在TcX有一个更大的需求,一个比100%通用数据库的真正快速的数据库。无论何时我们发现一个方法来实现这些特征而没有任何速度损失,我们将可能做它。暂时,有许多更重要的事情要做。检查TODO,看我们此时如何将事情优先排列。(有的较高级别支持的客户可以改变它,因此事情是可以重新优先化的。)
当前的问题实际上是ROLLBACK
,没有ROLLBACK
,你能用LOCK
TABLES
做任何COMMIT
动作。为了支持ROLLBACK
,MySQL将必须被改变以存储所有的旧记录,如果发出ROLLBACK
,它们被更新的并且将任何东西恢复到起点。对于简单的情形,这不是难做的
(当前isamlog
可以用于此目的),但是为ALTER/DROP/CREATE TABLE
实现ROLLBACK
将是更困难的。
避免使用ROLLBACK
,你可以使用下列策略:
LOCK TABLES ...
锁住所有你想要存取的数据库表。 UNLOCK TABLES
释放你的锁。 这通常比使用可能带ROLLBACK
的交易是一个更快的方法,尽管不总是这样。这个解决方案不能处理的唯一状况是当某人在更新当中杀死线程时。在这种情况下,所有的锁将被释放,但是一些更改不能被执行。
你也可使函数以单个操作更新记录。你能通过使用下列技术得到一个很有效率的应用程序:
例如,当我们正在更新一些客户信息时,我们仅仅更新那些改变了的客户数据并只测试没有任何数据的改变,或数据取决于改变的数据,与原来的行相比变化了。对于改变了的数据的测试用WHERE
子句在UPDATE
语句中完成。如果记录没被更新,我们给客户一条消息:“你改变了的一些数据已被其他用户改变了”,然后我们在一个窗口中显示新行对照旧行,因此用户能决定他该使用哪个版本的客户记录。
这给了我们类似于“列锁定”的东西,但是实际上甚至更好,因为我们仅仅更新某些列,使用相对于他们的当前值的值。这意味着典型的UPDATE
语句看上去象这些一样东西:
UPDATE tablename SET pay_back=pay_back+'relative change'; UPDATE customer SET customer_date='current_date', address='new address', phone='new phone', money_he_owes_us=money_he_owes_us+'new_money' WHERE customer_id=id AND address='old address' AND phone='old phone';
正如你能看到的,这是很有效的并且就算其他客户已经改变了pay_back
或money_he_owes_us
列的也能工作。
在许多情况下,为管理一些表格的唯一标识符目的,用户已经想要ROLLBACK
或LOCK
TABLES
。这可用一个AUTO_INCREMENT
列和一个SQL函数LAST_INSERT_ID()
或C
API函数mysql_insert_id()
更高效地处理。见20.4.29 mysql_insert_id()
。
在TcX,我们从来没有任何对行级锁定的需求,因为我们总是能通过编码解决它。一些情况下需要确实行锁定,但是他们是很少见的。如果你想要行级锁定,你可以在表中使用标志列并且这样做:
UPDATE tbl_name SET row_flag=1 WHERE id=ID;
如果行被找到发现并且row_flag
在原来的行已经不是1,对受影响的行数MySQL返回1。
你可以想到它,因为MySQL把上面的查询变为:
UPDATE tbl_name SET row_flag=1 WHERE id=ID and row_flag <> 1;