当前位置: 首页 > 文档资料 > MySQL 中文手册 >

1.8. MySQL标准的兼容性

优质
小牛编辑
138浏览
2023-12-01
1.8.1. MySQL遵从的标准是什么
1.8.2. 选择SQL模式
1.8.3. 在ANSI模式下运行MySQL
1.8.4. MySQL对标准SQL的扩展
1.8.5. MySQL与标准SQL的差别
1.8.6. MySQL处理约束的方式

在本节中,介绍了MySQL与ANSI/ISO SQL标准的关系。MySQL服务器有很多对SQL标准的扩展之处,这里介绍了它们是什么,以及使用它们的方法。你也能了解关于MySQL服务器缺失功能的信息,以及如何处理某些差异的方法。

SQL标准自1986年以来不断演化发展,有数种版本。在本手册中,“SQL-92”指得是1992年发布的标准,“SQL:1999”指得是1999年发布的标准,“SQL:2003”指得是标准的当前版本。我们采用术语“SQL标准”标示SQL标准的当前版本。

我们的目标是在没有良好理由的情况下不限制MySQL服务器的可用性。即使我们没有足够的资源就每种可能的应用进行开发,我们始终愿意帮助那些在新领域使用MySQL服务器的人员,并向他们提供建议。

对于该产品,我们的一项主要目标是,继续致力于与SQL标准的兼容性,但不以牺牲速度和可靠性为代价。如果它们能显著增加拥有大量用户基数的MySQL服务器的可用性,我们无惧于为SQL添加扩展,也无惧于为非SQL特性提供支持。MySQL服务器4.0中的HANDLER接口即是该策略的例子。请参见13.2.3节,“HANDLER语法”。

我们将继续支持事务性和非事务性数据库,以满足任务关键型全天候应用,以及高负载Web或日志应用。

MySQL服务器最初是为小型计算机系统上中等规模的数据库设计的(100万-1亿行,或每个表的大小为100MB)。目前,MySQL服务器能处理TB级别的数据库,也能在针对便携式设备或嵌入式设备的精简版本中使用。MySQL服务器的精简设计使得双向开发成为可能,不会在源码树中产生任何冲突。

目前,我们并未定位于实时支持,虽说MySQL复制特性提供了强大的功能。

在众多第三方簇解决方案中均有数据库簇支持特性,自4.1.2版以来,对于我们所需的NDB簇技术集成方案,同样请参见第17章:MySQL簇

我们也正着手在数据库服务器中提供XML支持。

1.8.1. MySQL遵从的标准是什么

我们致力于支持全套ANSI/ISO SQL标准,但不会以牺牲代码的速度和质量为代价。

ODBC级别0-3.51。

1.8.2. 选择SQL模式

MySQL服务器能够工作在不同的SQL模式下,并能针对不同的客户端以不同的方式应用这些模式。这样,应用程序就能对服务器操作进行量身定制以满足自己的需求。

这类模式定义了MySQL应支持的SQL语法,以及应该在数据上执行何种确认检查。这样,就能在众多不同的环境下、与其他数据库服务器一起更容易地使用MySQL。

可以使用“--sql-mode="modes"”选项,通过启动mysqld来设置默认的SQL模式。从MySQL 4.1开始,也能在启动之后,使用ET [SESSION|GLOBAL] sql_mode='modes'语句,通过设置sql_mode变量更改模式。

关于设置服务器模式的更多信息,请参见5.3.2节,“SQL服务器模式”。

1.8.3. 在ANSI模式下运行MySQL

你可以使用“--ansi”启动选项,要求mysqld使用ANSI模式。请参见5.3.1节,“mysqld命令行选项”。

在ANSI模式下运行服务器与使用该选项启动它的效果一样(在一行上指定“--sql_mode”值):

--transaction-isolation=SERIALIZABLE
--sql-mode=REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,
IGNORE_SPACE

在MySQL 4.1中,能够用下述两条语句实现相同的效果(在一行上指定“sql_mode”值):

SET GLOBAL TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SET GLOBAL sql_mode = 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,
IGNORE_SPACE';

请参见1.8.2节,“选择SQL模式”。

在MySQL 4.1.1中,也能用下述语句设置sql_mode选项:

SET GLOBAL sql_mode='ansi';

在本例中,将sql_mode变量的值设置为与ANSI模式相关的所有选项。你可以检查其结果,如下所示:

mysql> SET GLOBAL sql_mode='ansi';
mysql> SELECT @@global.sql_mode;
-> 'REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,
    IGNORE_SPACE,ANSI';

1.8.4. MySQL对标准SQL的扩展

MySQL服务器包含一些其他SQL DBMS中不具备的扩展。注意,如果使用了它们,将无法把代码移植到其他SQL服务器。在某些情况下,你可以编写包含MySQL扩展的代码,但仍保持其可移植性,方法是用“/*... */”注释掉这些扩展。在本例中,MySQL服务器能够解析并执行注释中的代码,就像对待其他MySQL语句一样,但其他SQL服务器将忽略这些扩展。例如:

SELECT /*! STRAIGHT_JOIN */ col_name FROM table1,table2 WHERE ...

如果在字符“!”后添加了版本号,仅当MySQL的版本等于或高于指定的版本号时才会执行注释中的语法:

CREATE /*!32302 TEMPORARY */ TABLE t (a INT);

这意味着,如果你的版本号为3.23.02或更高,MySQL服务器将使用TEMPORARY关键字。

下面按类别介绍了各种MySQL扩展。

·磁盘上的数据组织

MySQL服务器会将每个数据库映射到MySQL数据目录下的1个目录中,并将数据库中的表映射到数据库目录下的文件名。它具有下述含义:

o如果操作系统的文件名区分大小写(如大多数Unix系统),当MySQL服务器运行在这类操作系统上时,数据库名和表名也区分大小写。请参见9.2.2节,“识别符大小写敏感性”。

o你可以使用标准的系统命令来备份、重命名、移动、删除、并拷贝由MyISAM或ISAM存储引擎管理的表。例如,要想重命名MyISAM表,可重命名表对应的.MYD、.MYI、以及.frm文件。

数据库、表、索引、列或别名能够以数字开头(但或许不能全部由数字构成)。

·通用语言语法

o可以使用“””或“’”括住字符串,而不仅是“’”。

o在字符串中使用“\”作为转义字符。

o在SQL语句中,可以使用“db_name.tbl_name”语法访问不同数据库中的表。某些SQL服务器提供了相同的功能,但调用该用户空间除外。MySQL服务器不支持表空间,如下述语句中使用的那样: CREATE TABLE ralph.my_table...IN my_tablespace.

·SQL语句的语法

oANALYZE TABLE,CHECK TABLE,OPTIMIZE TABLE,以及REPAIR TABLE语句。

oCREATE DATABASE和DROP DATABASE语句。请参见13.1.3节,“CREATE DATABASE语法”。

oDO语句。

oEXPLAIN SELECT获取如何联合表的介绍。

oFLUSH和RESET语句。

oSET语句。请参见13.5.3节,“SET语法”。

oSHOW语句。请参见13.5.4节,“SHOW语法”。

o使用LOAD DATA INFILE。在很多情况下,该语法与Oracle的LOAD DATA INFILE兼容。请参见13.2.5节,“LOAD DATA INFILE语法”。

oRENAME TABLE的使用。请参见13.1.9节,“RENAME TABLE语法”。

o使用REPLACE取代DELETE + INSERT。请参见13.2.6节,“REPLACE语法”。

o在ALTER TABLE语句中使用CHANGE col_name、DROP col_name、或DROP INDEX、IGNORE或RENAME。在ALTER TABLE语句中使用多个ADD、ALTER、DROP或CHANGE子句。请参见13.1.2节,“ALTER TABLE语法”。

o使用索引名,字段前缀上的索引,并在CREATE TABLE语句中使用INDEX或KEY。请参见13.1.5节,“CREATE TABLE语法”。

o与CREATE TABLE一起使用TEMPORARY或IF NOT EXISTS。

o与DROP TABLE一起使用IF EXISTS。

o使用单个DROP TABLE语句,能够舍弃多个表。

oUPDATE和DELETE语句的ORDER BY和LIMIT子句。

oINSERT INTO ... SET col_name = ... syntax.

oINSERT和REPLACE语句的DELAYED子句。

oINSERT、REPLACE、DELETE和UPDATE语句的LOW_PRIORITY子句。

o在SELECT语句中使用INTO OUTFILE和STRAIGHT_JOIN。请参见13.2.7节,“SELECT语法”。

oSELECT语句中的SQL_SMALL_RESULT选项。

o不需要在GROUP BY部分命名所有选择的列。对于某些十分特殊但相当正常的查询,它能提供更好的性能。请参见12.10节,“与GROUP BY子句同时使用的函数和修改程序”。

o可以与GROUP BY一起指定ASC和DESC。

o能够在带有“:=”赋值操作符的语句中设置变量。

o     mysql> SELECT @a:=SUM(total),@b=COUNT(*),@a/@b AS avg
o -> FROM test_table;
o     mysql> SELECT @t1:=(@t2:=1)+@t3:=4,@t1,@t2,@t3;

·列类型

o列类型MEDIUMINT、SET、ENUM、以及不同的BLOB和TEXT类型。

o列属性AUTO_INCREMENT、BINARY、NULL、UNSIGNED以及ZEROFILL。

·函数和操作符

o为了使其他SQL环境下的用户更容易入手,MySQL服务器对很多函数均支持别名特性。例如,所有的字符串函数均支持标准SQL语法和ODBC语法。

oMySQL服务器能够理解“||”和“&&”操作符,将其当作逻辑OR和AND,就像在C编程语言中那样。在MySQL服务器中,||和OR是同义词,&&和AND也是同义词。由于采用了该优异的语法体系,MySQL服务器不支持SQL针对字符串连接的“||”操作符,而采用了CONCAT()取而代之。由于CONCAT()能够接受任意数目的参量,很容易将使用“||”操作符的情况转换为MySQL服务器支持的类型。

o请在有多于1个元素的场合下使用COUNT(DISTINCT list)。

o默认情况下,所有的字符串比较均区分大小写,其分类顺序由当前字符集确定(默认为cp1252 Latin1)。如果你不喜欢该点,应使用BINARY属性或BINARY cast声明列,这样,就会使用基本的字符代码值进行比较,而不是词汇顺序。

o“%”操作符等同于MOD()。也就是说“N % M”等同于MOD(N,M)。Cyuyan的程序员支持“%”,而且它也是为了兼容PostgreSQL而使用的。

o在列比较中,可在SELECT语句的FROM左侧使用=、<>、<=、<、>=、>、<<、>>、<=>、AND、OR或LIKE操作符。例如:

o     mysql> SELECT col1=1 AND col2=2 FROM tbl_name;

o返回最近AUTO_INCREMENT值的LAST_INSERT_ID()函数。请参见12.9.3节,“信息函数”。

o允许在数值列上使用LIKE。

oREGEXP和NOT REGEXP扩展了常规的表达式操作符。

o具有1个或2个以上参量的CONCAT()或CHAR()。(在MySQL服务器中,这些函数可以有任意数目的参量)。

oBIT_COUNT()、CASE、ELT()、FROM_DAYS()、FORMAT()、IF()、PASSWORD()、ENCRYPT()、MD5()、ENCODE()、DECODE()、PERIOD_ADD()、PERIOD_DIFF()、TO_DAYS()、以及WEEKDAY()函数。

o使用TRIM()来调整子字符串。标准SQL仅支持单个字符的删除。

GROUP BY函数STD()、BIT_OR()、BIT_AND()、BIT_XOR()、以及GROUP_CONCAT()。请参见12.10节,“与GROUP BY子句同时使用的函数和修改程序”。

1.8.5. MySQL与标准SQL的差别

1.8.5.1. 子查询
1.8.5.2. SELECT INTO TABLE
1.8.5.3. 事务和原子操作
1.8.5.4. 存储程序和触发程序
1.8.5.5. 外键
1.8.5.6. 视图
1.8.5.7. ‘--’作为注释起始标记

我们试图使MySQL服务器遵从ANSI SQL标准和ODBC SQL标准,但在某些情况下MySQL服务器执行的操作有所不同:

·对于VARCHAR列,存储值时删除了尾部空间。(在MySQL 5.0.3中更正)。请参见A.8节,“MySQL中的已知事宜”。

·在某些情况下,定义表或更改其结构时,将CHAR列转换为VARCHAR列。(在MySQL 5.0.3中更正)。请参见13.1.5.1节,“沉寂的列规格变更”。

·删除表时,不自动取消关于表的权限。必须明确发出REVOKE语句,以撤销针对表的权限。请参见13.5.1.3节,“GRANT和REVOKE语法”。

·CAST()函数不支持对REAL或BIGINT的抛弃。请参见12.8节,“Cast函数和操作符”。

·标准SQL要求,SELECT语句中的HAVING子句能够引用GROUP BY子句中的列。在MySQL 5.0.2之前,不能完成该功能。

1.8.5.1. 子查询

MySQL 4.1支持子查询和导出表。“子查询”指的是嵌套在另一语句中的SELECT语句。“导出表”(未命名视图)是另一语句的FROM子句中的子查询。请参见13.2.8节,“Subquery语法”。

从MySQL 4.1版起,可以使用联合或其他方法重写大多数子查询。关于如何完成该任务的更多信息,请参见13.2.8.11节,“对于较早的MySQL版本,采用联合方法重写子查询”。

1.8.5.2. SELECT INTO TABLE

MySQL服务器不支持Sybase SQL扩展: SELECT ... INTO TABLE ....。但MySQL服务器支持标准的SQL语法INSERT INTO ... SELECT ...,它基本上相同。请参见13.2.4.1节,“INSERT ... SELECT语法”。

INSERT INTO tbl_temp2 (fld_id)
    SELECT tbl_temp1.fld_order_id
    FROM tbl_temp1 WHERE tbl_temp1.fld_order_id > 100;

作为备选方式,可以使用SELECT INTO OUTFILE ...或CREATE TABLE ... SELECT。

从5.0版开始,MySQL支持SELECT ... INTO,以及用户变量。在使用光标和局部变量的存储程序中也可以使用相同的语法。请参见20.2.9.3节,“SELECT ... INTO语句”。

1.8.5.3. 事务和原子操作

MySQL服务器(3.23至该系列的最高版本,所有4.0版本,以及更高版本)支持采用InnoDB和BDB事务存储引擎的事务。InnoDB提供了全面的ACID兼容性。请参见第15章:存储引擎和表类型

MySQL服务器中的其他非事务性存储引擎(如MyISAM)遵从不同的数据完整性范例,称之为“原子操作”。按照事务术语,MyISAM表总能高效地工作在AUTOCOMMIT=1模式下。原子操作通常能提供可比较的完整性以及更好的性能。

由于MySQL服务器支持两种范例,因而你能决定是否利用原子操作的速度更好地服务于你的应用程序,或使用事务特性。该选择可按表进行。

正如所阐述的那样,事务性和非事务性表类型之间的权衡主要取决于性能。事务性表对内存和磁盘空间的要求更高,CPU开销也更大。另一方面,多种事务性表类型,如InnoDB,也能提供很多显著特性。MySQL服务器的模块化设计允许同时使用不同的存储引擎,以满足不同的要求,并在所有情形下,提供最佳性能。

但是,即便使用非事务性MyISAM表,你将如何使用MySQL服务器的特性来保持严格的完整性呢?这些特性与事务性表类型相比又如何呢?

1. 如果应用程序采用了特定的编写方式,依赖于在关键情况下能够调用ROLLBACK而不是COMMIT,那么事务性类型更方便。使用事务,还能确保未完成的更新或崩溃的活动不被提交到数据库,能为服务器提供自动回滚的机会,并保存你的数据库。

如果使用非事务性表,MySQL服务器几乎在所有情况下均允许你解决潜在的问题,方式是在更新前进行简单检查,并运行检查数据库一致性的简单脚本,如果出现不一致性,该脚本能自动修复它或给出告警。注意,仅使用MySQL日志或增加额外日志,通常能完美地更正表,同时不会造成数据完整性损失。

2. 在很多情况下,能够对关键的事务更新进行重写,使之成为“原子”类型。一般而言,所有由事务解决的完整性问题均能用LOCK TABLES或原子更新解决,从而确保了服务器不会自动中断,后者是事务性数据库系统的常见问题。

3. 为了安全使用MySQL服务器,无论是否使用事务性表,仅需启用备份和二进制日志功能。这样,你就能解决使用其他事务性数据库系统时遇到的任何问题。无论使用的数据库系统是什么,启用备份总是个好主意。

事务范型有自己的优点和不足之处。很多用户和应用程序开发人员喜欢这类简单性,在出现问题时或必要时,通过代码解决问题。但是,即使你是原子操作范型的新手,或更熟悉事务,也请考虑非事务性表的速度益处,与经过优化调整的最快的事务性表相比,它的速度快3~5倍。

在完整性具有最高重要性的情况下,即使是对非事务性表,MySQL也能提供事务级别的可靠性和安全性。如果使用LOCK TABLES锁定了表,所有更新均将被暂时中止直至完整性检查完成。如果你获得了对某一表的READ LOCAL锁定(与写锁定相对),该表允许在表尾执行并行插入,当其他客户端执行插入操作时,允许执行读操作。新插入的记录不会被有读锁定属性的客户端看到,直至解除了该锁定为止。使用INSERT DELAYED,能够将插入项置于本地队列中,直至锁定解除,不会让客户端等待插入完成。请参见13.2.4.2节,“INSERT DELAYED语法”。

从我们赋与其名称的意义上,“原子”绝非不可思议的。它仅意味着,你能确信在每个特性更新运行的同时,其他用户不能干涉它,而且不会出现自动回滚(如果你不小心,对于事务性表,这种情况可能发生)。MySQL服务器还能保证不存在脏读。

下面列出了使用非事务性表的一些技术:

·对于需要事务的循环,通常能使用LOCK TABLES进行编码,不需要光标来更新正在处理的记录。

·要想避免使用ROLLBACK,可采取下述策略:

1. 使用LOCK TABLES锁定所有希望访问的表。

2. 执行更新前,测试必须为真的条件。

3. 如果一切正常,执行更新。

4. 使用UNLOCK TABLES解除锁定。

与使用具有回滚可能性的事务性表相比,它通常具有更快的速度,虽然并非始终如此。该解决方案唯一不能处理的情形是,在更新中途杀死了线程。在这种情况下,将释放所有锁定,但某些更新可能尚未执行。

·也可以使用函数在单一操作中更新记录。采用下述技术,能获得效率很高的应用程序。

o 根据其当前值更改列。

o 仅更新出现实际变化的列。

例如,当我们更新某些客户信息时,仅更新已更改的客户数据,与原始行相比,仅测试已更改的数据或依赖于已更改数据的数据是否未出现变化。对于已更改数据的测试,它是通过UPDATE语句的WHERE子句完成的。如果记录未更新,将向客户端发出消息: “一些你改变的数据已被其他用户更改”。接下来,我们在窗口中给出了旧行和新行,以便用户决定使用哪个版本。

这给出了与列锁定类似的结果,但效果更好,使用相对于其当前值的值,仅更新了某些列。这意味着,典型的UPDATE语句与下面给出的类似:

UPDATE tablename SET pay_back=pay_back+125;
 
UPDATE customer
  SET
    customer_date='current_date',
    address='new address',
    phone='new phone',
    money_owed_to_us=money_owed_to_us-125
  WHERE
    customer_id=id AND address='old address' AND phone='old phone';

它很有效,即使其他客户端更改了pay_back或money_owed_to_us列中的值,也能使用。

· 在很多情况下,用户希望将LOCK TABLES和/或ROLLBACK用于管理唯一ID。可以在不使用锁定功能或回滚的情况下,使用AUTO_INCREMENT列以及LAST_INSERT_ID() SQL函数或mysql_insert_id() C API函数,更有效地处理之。请参见12.9.3节,“信息函数”。请参见25.2.3.36节,“mysql_insert_id()”。

我们通常能使用代码来处理行级锁定方面的需求。在某些情况下,实际上不需要它,InnoDB表支持行级锁定。通过MyISAM表,能够在表中使用标志列,并完成类似下面的操作:

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;

1.8.5.4. 存储程序和触发程序

对于MySQL,在5.0版本中实现了存储程序。请参见第20章:存储程序和函数

从5.0.2版开始,在MySQL中实现了基本的触发器功能,计划在MySQL 5.1中进一步发展它。请参见第21章:触发程序

1.8.5.5. 外键

在MySQL服务器3.23.44和更高版本中,InnoDB存储引擎支持对外键约束的检查功能,这些约束包括CASCADE、ON DELETE和ON UPDATE。请参见15.2.6.4节,“FOREIGN KEY约束”。

对于InnoDB之外的其他存储引擎,MySQL服务器能够解析CREATE TABLE语句中的FOREIGN KEY语法,但不能使用或保存它。未来将进行扩展,能够将这类信息保存到表规范文件中,以便能被mysqldump和ODBC检索。稍后,还将为MyISAM表实现外键约束。

外键增强为数据库开发人员提供了多项益处:

·假定关联设计恰当,外键约束使得程序员更难将不一致性引入数据库。

·数据库服务器具有集中式约束检查功能,因而没有必要在应用程序一侧执行这类检查。这样,就消除了不同应用程序使用不同方式检查约束的可能性。

·使用级联更新和删除,简化了应用程序代码。

·设计恰当的外键有助于以文档方式记录表间的关系。

请记住,这些好处是以数据库服务器为执行必要检查而需的额外开销为代价的。服务器额外检查会影响性能,对于某些应用程序,该特性不受欢迎,应尽量避免。(出于该原因,在一些主要的商业应用程序中,在应用程序级别上实施了外键逻辑)。

MySQL允许数据库开发人员选择要使用的方法。如果你不需要外键,并希望避免与强制引用完整性有关的开销,可选择另一种表类型取而代之,如MyISAM。(例如,MyISAM存储引擎为仅执行INSERT和SELECT操作的应用程序提供了极快的性能,这是因为插入能和检索同时进行)。请参见7.3.2节,“表锁定事宜”。

如果你不打算利用引用完整性检查具备的优点,请记住下述要点:

·不存在服务器端外键关联检查时,应用程序本身必须处理这类关联事宜。例如,将行按恰当顺序插入表时应谨慎,并应避免产生孤立的子记录。必须能够在多记录插入操作期间更正出现的错误。

·如果ON DELETE是应用程序所需的唯一引用完整性功能,请注意,从MySQL服务器4.0起,可以使用多表DELETE语句,用单一语句从多个表中删除行。请参见13.2.1节,“DELETE语法”。

·从具有外键的表删除记录时,在缺少ON DELETE的情况下,一种解决方式是为应用程序增加恰当的DELETE语句。实际上,它与使用外键同样快,而且移植性更好。

注意,使用外键在某些情况下会导致问题。

·外键支持能处理很多引用完整性事宜,但仍需要仔细设计键的关系,以避免循环规则或不正确的级联删除组合。

·DBA需要创建关联拓扑,这会使从备份中恢复单独表变得困难,该类情形并不罕见。(加载依赖其他表的表时,MySQL允许你临时禁止外键检查,从而降低了该难度)。请参见15.2.6.4节,“FOREIGN KEY约束”。在MySQL 4.1.1以前。重新加载时,mysqldump能够生成自动利用该性能的转储文件。

注意,SQL中的外键用于检查和强制引用完整性,而不是联合表。如果打算用SELECT语句获取多个表的结果,可在表之间执行联合操作:

SELECT * FROM t1, t2 WHERE t1.id = t2.id;

请参见13.2.7.1节,“JOIN语法”。请参见3.6.6节,“使用外键”。

ODBC应用程序常使用不带“ON DELETE ...”的FOREIGN KEY语法来生成自动WHERE子句。

1.8.5.6. 视图

在MySQL服务器5.0版中实现了视图功能(包括可更新视图)。在5.0.1和更高版本中,提供了二进制版的视图功能。请参见第22章:视图

View(视图)十分有用,它允许用户像单个表那样访问一组关系(表),而且仅允许对它们的这类访问。视图也能限制对行的访问(特定表的子集)。对于列控制的访问,可使用MySQL服务器中的高级权限系统。请参见5.7节,“MySQL访问权限系统”。

在设计视图的过程中,我们的宏伟目标是,在SQL的范围内尽可能与关联数据库系统的“Codd's Rule #6”兼容。“所有理论上可更新的视图,实际上也应是可更新的”。

1.8.5.7. ‘--’作为注释起始标记

一些其他SQL数据库采用“--”作为注释开始标志。MySQL服务器采用“#”作为注释起始字符。对于MySQL服务器,也能使用C风格的注释:/*该处为注释*/。请参见9.5节,“注释语法”。

MySQL服务器3.23.3和更高版本支持“--”注释风格,但要求注释后面跟1空格(或控制字符,如新行)。之所以要求使用空格,是为了防止与自动生成SQL查询有关的问题,它采用了类似下面的代码,其中,自动为“!payment!”插入“payment”的值:

UPDATE account SET credit=credit-!payment!

考虑一下,如果“payment”的值为负数如“-1”时会出现什么情况:

UPDATE account SET credit=credit--1

在SQL中“credit--1”是合法的表达式,但是,如果“--1”被解释为注释开始,部分表达式将被舍弃。其结果是,表达式的意义与预期的意义完全不同。

UPDATE account SET credit=credit

该语句不会对值作任何更改!这表明,允许注释以“--”开始会产生严重后果。

采用MySQL服务器3.23.3和更高版本中的这类注释方法,“credit--1”实际上很安全。

另一个安全特性是,mysql命令行客户端将删除所有以“--”开头的行。

仅当使用高于3.23.3的MySQL时,下述信息才有意义:

如果有1个文本文件形式的SQL程序,该文件包含“--”注释,应按下述方式使用replace实用工具,将其转换为使用“#”字符的注释:

shell> replace " --" " #" < text-file-with-funny-comments.sql \
 | mysql db_name

而不是通常的:

shell> mysql db_name < text-file-with-funny-comments.sql

你也可以编辑注释文件,将“--”注释更改为“#”注释:

shell> replace " --" " #" -- text-file-with-funny-comments.sql

使用下述命令将其改回去:

shell> replace " #" " --" -- text-file-with-funny-comments.sql

1.8.6. MySQL处理约束的方式

1.8.6.1. PRIMARY KEY和UNIQUE索引约束
1.8.6.2. 对无效数据的约束
1.8.6.3. ENUM和SET约束

使用MySQL,你可以使用允许回滚的事务表,以及不允许回滚的非事务表。因此,在MySQL中的约束处理功能与其他DBMS中的略有不同。在非事务性表中插入或更新大量行时,当出现错误以至于不能回滚所作的变更时,必须处理该情况。

其基本原理在于,在解析将要执行的语句的同时,MySQL服务器会尽量为检测到的问题生成错误信息,并会在执行语句的同时尽量恢复出现的错误。在大多数情况下我们均是这样作的,但不包括所有情况。

出现错误时,MySQL可选择中途中止语句,或尽可能恢复并继续执行语句。默认情况下,服务器将采取后一种路线。这意味着,服务器可能会强制将非法值变为最接近的合法值(例如)。

从MySQL 5.0.2开始,提供了数种SQL模式,使用它们,能够对如何接受可能为不良数据值的方式进行更好的控制,也能在出现错误时,对是否继续执行语句或放弃语句进行控制。使用这些选项,能够将MySQL服务器配置为更为传统的风格,类似于拒绝不恰当输入的其他DBMS。可以在运行时设置SQL模式,这样,各客户端就能选择与其需求最为贴切的行为。请参见5.3.2节,“SQL服务器模式”。

在以下部分,介绍了使用不同约束类型的情况。

1.8.6.1. PRIMARY KEY和UNIQUE索引约束

通常情况下,当你试图INSERT或UPDATE会导致主键、唯一键或外键冲突的行时,将出现错误。如果你正在使用事务性存储引擎时,如InnoDB,MySQL会自动回滚语句。如果你正在使用非事务性存储引擎,MySQL将在出错的行上停止执行语句,剩余的行也不再处理。

如果你希望忽略这类键冲突,可使用MySQL支持的、用于INSERT和UPDATE的IGNORE关键字。在这种情况下,MySQL将忽略任何键冲突,并继续处理下一行。请参见13.2.4节,“INSERT语法”。请参见3.2.10节,“UPDATE语法”。

使用mysql_info() C API函数,能够获取关于实际插入或更新行数的信息。请参见25.2.3.34节,“mysql_info()”。在MySQL 4.1和更高版本中,也能使用SHOW WARNINGS语句。请参见13.5.4.22节,“SHOW WARNINGS语法”。

目前,只有InnoDB表支持外键。请参见15.2.6.4节,“FOREIGN KEY约束”。计划在MySQL 5.1中实施对MyISAM表的外键支持。

1.8.6.2. 对无效数据的约束

在MySQL 5.0.2之前,MySQL对非法或不当值并不严厉,而且为了数据输入还会强制将它们变为合法值。在MySQL 5.0.2和更高版本中,保留了以前的默认行为,但你可以为不良值选择更传统的处理方法,从而使得服务器能够拒绝并放弃出现不良值的语句。本节介绍了MySQL的默认行为(宽大行为),新的严格的SQL模式,以及它们的区别。

如果你未使用严格模式,下述情况是真实的。如果将“不正确”的值插入到列,如将NULL值插入非NULL列,或将过大的数值插入数值列,MySQL会将这些列设置为“最可能的值”,而不是生成错误信息。

·如果试图将超范围的值保存到数值列,MySQL服务器将保存0(最小的可能值)取而代之,或最大的可能值。

·对于字符串,MySQL或保存空字符串,或将字符串尽可能多的部分保存到列中。

·如果打算将不是以数值开头的字符串保存到数值列,MySQL将保存0。

·MySQL允许将特定的不正确日期值保存到DATE和DATETIME列(如“2000-02-31”或“2000-02-00”)。其观点在于,验证日期不是SQL服务器的任务。如果MySQL能保存日期值并准确检索相同的值,MySQL就能按给定的值保存它。如果日期完全不正确(超出服务器能保存的范围)将在列中保存特殊的日期值“0000-00-00”取而代之。

·如果试图将NULL值保存到不接受NULL值的列,对于单行INSERT语句,将出现错误。对于多行INSERT语句或INSERT INTO ... SELECT语句,MySQL服务器会保存针对列数据类型的隐含默认值。一般情况下,对于数值类型,它是0,对于字符串类型,它是空字符串(''),对于日期和时间类型是“zero”。在13.1.5节,“CREATE TABLE语法”一节中,讨论了隐含的默认值。

·如果INSERT语句未为列指定值,如果列定义包含明确的DEFAULT子句,MySQL将插入默认值。如果在定义中没有这类DEFAULT子句,MySQL会插入列数据类型的隐含默认值。

采用前述规则的原因在于,在语句开始执行前,无法检查这些状况。如果在更新了数行后遇到这类问题,我们不能仅靠回滚解决,这是因为存储引擎可能不支持回滚。中止语句并不是良好的选择,在该情况下,更新完成了“一半”,这或许是最差的情况。对于本例,较好的方法是“仅可能做到最好”,然后就像什么都未发生那样继续。

在MySQL 5.0.2和更高版本中,可以使用STRICT_TRANS_TABLES或STRICT_ALL_TABLES SQL模式,选择更严格的处理方式。请参见5.3.2节,“SQL服务器模式”。

STRICT_TRANS_TABLES的工作方式:

·对于事务性存储引擎,在语句中任何地方出现的不良数据值均会导致放弃语句并执行回滚。

·对于非事务性存储引擎,如果错误出现在要插入或更新的第1行,将放弃语句。(在这种情况下,可以认为语句未改变表,就像事务表一样)。首行后出现的错误不会导致放弃语句。取而代之的是,将调整不良数据值,并给出告警,而不是错误。换句话讲,使用STRICT_TRANS_TABLES后,错误值会导致MySQL执行回滚操作,如果可以,所有更新到此为止。

要想执行更严格的检查,请启用STRICT_ALL_TABLES。除了非事务性存储引擎,它与STRICT_TRANS_TABLES等同,即使当不良数据出现在首行后的其他行,所产生的错误也会导致放弃语句。这意味着,如果错误出现在非事务性表多行插入或更新过程的中途,仅更新部分结果。前面的行将完成插入或更新,但错误出现点后面的行则不然。对于非事务性表,为了避免这种情况的发生,可使用单行语句,或者在能接受转换警告而不是错误的情况下使用STRICT_TRANS_TABLES。要想在第1场合防止问题的出现,不要使用MySQL来检查列的内容。最安全的方式(通常也较快)是,让应用程序负责,仅将有效值传递给数据库。

有了严格的模式选项后,可使用INSERT IGNORE或UPDATE IGNORE而不是不带IGNORE的INSERT或UPDATE,将错误当作告警对待。

1.8.6.3. ENUM和SET约束

ENUM和SET列提供了定义仅能包含给定值集合的列的有效方式。但是,从MySQL 5.0.2起,ENUM和SET不是实际约束。其原因与不重视NOT NULL的原因一样。请参见1.8.6.2节,“对无效数据的约束”。

ENUM列总有1个默认值。如果未指定默认值,对于包含NULL的列,默认值为NULL;否则,第1个枚举值将被当作默认值。

如果在ENUM列中插入了不正确的值,或者,如果使用IGNORE将值强制插入了ENUM列,会将其设置为保留的枚举值0,对于字符串情形,将显示为空字符串。请参见11.4.4节,“ENUM类型”。

如果在SET列中插入了不正确值,该值将被忽略。例如,如果列能包含值“a”、“b”和“c”,并赋值“a,x,b,y”,结果为“a,b”。请参见11.4.5节,“SET类型”。

从5.0.2开始,可以对服务器进行配置,以使用严格的SQL模式。请参见5.3.2节,“SQL服务器模式”。启用严格模式后,ENUM或SET列的定义可作为对输入至列的值的约束。如果值不满足下述条件,将出现错误:

·ENUM值必须是在列定义中给出的值之一,或内部的数字等同物。该值不能是错误值(即,0或空字符串)。对于定义为ENUM('a','b','c')的列,诸如''、'd'和'ax'等,均是非法的,并将被拒。

·SET值必须是空字符串,或由1个或多个在列定义中给出的且用逗号隔开的值组成。 对于定义为SET('a','b','c')的列,诸如'd'和'a,b,c,d'等,均是非法的,并将被拒。

如果使用了INSERT IGNORE或UPDATE IGNORE,在严格模式下,可抑制无效值导致的错误。在这种情况下,将生成警告而不是错误。对于ENUM,值将作为错误成员(0)插入。对于SET,会将给定值插入,但无效的子字符串将被删除。例如,'a,x,b,y'的结果是'a,b',就像前面介绍的那样。

这是MySQL参考手册的翻译版本,关于MySQL参考手册,请访问dev.mysql.com。 原始参考手册为英文版,与英文版参考手册相比,本翻译版可能不是最新的。