ThinkSSL🔒 一键申购 5分钟快速签发 30天无理由退款 购买更放心 广告
### 附录I:特性限制 ** 目录** [ I.1. 对存储子程序和触发程序的限制](#)[ I.2. 对服务器端光标的限制](#)[ I.3. 对子查询的限制](#)[ I.4. 对视图的限制](#)[ I.5. 对XA事务的限制](#) 在本附录中,介绍了使用诸如子查询或视图等MySQL特性时存在的限制。 ### I.1. 对存储子程序和触发程序的限制 这里介绍的某些限制适用于所有的存储子程序,即存储程序和存储函数。某些限制仅适用于存储函数而不是存储程序。 对存储函数的的所有限制也适用于触发程序。 **注释:**如果SQL语句,如SELECT ... INTO语句包含具有相同名称的对列的引用以及声明的局部变量,MySQL会将引用解释为变量的名称。这是一种非标准的行为,优先顺序通常是列名、然后是SQL变量和参数。请参见[20.2.9.3节,“SELECT ... INTO语句”](# "20.2.9.3. SELECT ... INTO Statement")。 存储子程序不能包含任意SQL语句。在存储子程序中,禁止使用下述语句: ·         CHECK TABLES ·         LOCK TABLES, UNLOCK TABLES ·         LOAD DATA, LOAD TABLE ·         SQL预处理语句(PREPARE、EXECUTE、DEALLOCATE PREPARE)。隐含意义:不能在存储子程序中使用动态SQL语句(其中,能够以字符串形式构造动态语句,然后执行它们)。从MySQL 5.0.13开始,对于存储程序放宽了该限制,但该限制仍适用于存储函数和触发程序。 ·         OPTIMIZE TABLE 对于存储函数(而不是存储程序),禁止下述额外语句: ·         执行显式或隐式提交或回滚操作的语句。 ·         返回结果集的语句。包括没有INFO子句的SELECT语句,以及SHOW语句。能够用SELECT … INTO,或使用光标和FETCH语句处理结果集的函数。 ·         FLUSH语句。注意,尽管能够在存储程序中使用FLUSH,但不能从存储函数或触发程序调用这类存储程序。 注意,尽管某些限制在正常情况下适用于存储函数和触发程序,不适用于存储程序,如果它们是从存储函数或触发程序中调用的,这些限制也适用于存储程序。 使用存储子程序会导致复制问题。关于这方面的进一步讨论,请参见[20](#)[.4节,“存储子程序和触发程序的二进制日志功能”](# "20.4. Binary Logging of Stored Routines and Triggers")。 INFORMATION_SCHEMA尚不包含PARAMETERS表,因此,对于需要在运行时获取子程序参数信息的应用程序来说,必须采用相应的规避错误,如解析SHOW CREATE语句的输出。 没有存储子程序调试工具。 存储子程序使用了具体化的光标,而不是固有光标(在服务器端生成结果集并对结果集进行高速缓冲处理,然后在客户端获取结果集时按行返回)。 不能提前处理CALL语句。无论是对服务器端预处理语句还是SQL预处理语句,均成立。 为了防止服务器线程间的交互问题,当客户端发出语句时,服务器将使用可用的、用于语句执行的子程序和触发程序快照。也就是说,服务器将计算出可在语句执行期间使用的存储程序、函数和触发程序的列表,加载它们,然后进入语句执行。这意味着,在语句执行的同时,它不会看到其他线程对子程序所作的变更。 ### I.2. 对服务器端光标的限制 从MySQL 5.0.2开始,通过mysql_stmt_attr_set() C API函数实现了服务器端光标。服务器端光标允许在服务器端生成结果集,但不会将其传输到客户端,除非客户端请求这些行。例如,如果客户端执行了查询,但仅对第1行感兴趣,那么不会传输剩余的行。 光标是只读的,不能使用光标来更新行。 未实施UPDATE WHERE CURRENT OF和DELETE WHERE CURRENT OF,这是因为不支持可更新的光标。 光标是不可保持的(提交后不再保持打开)。 光标是不敏感的。 光标是不可滚动的。 光标是未命名的。语句处理程序起着光标ID的作用。 对于每条预处理语句,仅能打开1个光标。如果需要多个光标,必须处理多条语句。 如果在预处理模式下不支持语句,不能在生成结果集的语句上使用光标。包括CHECK TABLES、HANDLER READ和SHOW BINLOG EVENTS语句。 ### I.3. 对子查询的限制 随后将更正的一致缺陷:如果将NULL值与使用ALL、ANY或SOME的子查询进行比较,而且子查询返回空结果,比较操作将对NULL的非标准结果进行评估,而不是TRUE或FALSE。 子查询的外部语句可以是SELECT、INSERT、UPDATE、DELETE、SET或DO中的任何一个。 仅部分支持行比较操作: ·         对于*expr* IN (*subquery*),*expr*可以是*n*-tuple(通过行构造程序语法指定),而且子查询能返回*n*-tuples个行。 ·         对于*expr**op* {ALL|ANY|SOME} (*subquery*),*expr*必须是标度值,子查询必须是列子查询,不能返回多列行。 换句话讲,对于返回*n*-tuples行的子查询,支持: ~~~ (val_1, ..., val_n) IN (subquery) ~~~ 但不支持: ~~~ (val_1, ..., val_n) op {ALL|ANY|SOME} (subquery) ~~~ 支持针对IN的行比较,但不支持针对其他的行比较,原因在于,IN实施是通过将其重新编写为“=”比较和AND操作的序列完成的。该方法不能用于ALL、ANY或SOME。 未良好优化行构造程序。下面的两个表达式是等效的,但只有第2个表达式能被优化: ~~~ (col1, col2, ...) = (val1, val2, ...) ~~~ ~~~ col1 = val1 AND col2 = val2 AND ... ~~~ 对于IN的子查询优化不如对“=”的优化那样有效。 对于不良IN性能的一种典型情况是,当子查询返回少量行,但外部查询返回将与子查询结果相比较的大量行。 FROM子句中的子查询不能与子查询有关系。在评估外部查询之前,将对它们进行具体化处理(执行以生成结果集),因此,不能按照外部查询的行对它们进行评估。 一般而言,不能更改表,并从子查询内的相同表进行选择。例如,该限制适用于具有下述形式的语句: ~~~ DELETE FROM t WHERE ... (SELECT ... FROM t ...); ~~~ ~~~ UPDATE t ... WHERE col = (SELECT ... FROM t ...); ~~~ ~~~ {INSERT|REPLACE} INTO t (SELECT ... FROM t ...); ~~~ 例外:如果为FROM子句中更改的表使用子查询,前述禁令将不再适用。例如: ~~~ UPDATE t ... WHERE col = (SELECT (SELECT ... FROM t...) AS _t ...); ~~~ 禁令在此不适用,这是因为FROM中的子查询已被具体化为临时表,因此“t”中的相关行已在满足“t”条件的情况下、在更新时被选中。 与子查询相比,针对联合的优化程序更成熟,因此,在很多情况下,如果将其改写为join(联合),使用子查询的语句能够更有效地执行。 但下述情形例外:IN子查询可被改写为SELECT DISTINCT联合。例如: ~~~ SELECT col FROM t1 WHERE id_col IN (SELECT id_col2 FROM t2 WHERE condition); ~~~ 可将该语句改写为: ~~~ SELECT DISTINCT col FROM t1, t2 WHERE t1.id_col = t2.id_col AND condition; ~~~ 但在该情况下,联合需要额外的DISTINCT操作,而且与子查询相比,效率并不高。 可能的未来优化:MySQL不改写针对子查询评估的联合顺序。在某些情况下,如果MySQL将其改写为联合,能够更有效地执行子查询。这样,优化程序就能在更多的执行方案间进行选择。例如,它能决定是否首先读取某一表或其他。 例如: ~~~ SELECT a FROM outer_table AS ot ~~~ ~~~ WHERE a IN (SELECT a FROM inner_table AS it WHERE ot.b = it.b); ~~~ 对于该查询,MySQL总会首先扫描outer_table,如然后针对每一行在inner_table上执行子查询。如果outer_table有很多行而inner_table只有少量行,查询的执行速度或许要慢于本应有的速度。 前述查询可改写为: ~~~ SELECT a FROM outer_table AS ot, inner_table AS it ~~~ ~~~ WHERE ot.a = it.a AND ot.b = it.b; ~~~ 在该情况下,我们能扫描小的表(inner_table)并查询outer_table中的行,如果在“ot.a,ot.b”上有索引,速度会更快。 可能的未来优化:对外部查询的每一行评估关联的子查询。更好的方法是,如果外部行的值与之前的行相比没有变化,不对子查询进行再次评估,而是使用以前的结果。 可能的未来优化:通过将结果具体化到临时表,而且该表不使用索引,对FROM子句中的子查询进行评估。在查询中与其他表进行比较时,尽管可能是有用的,但不允许使用索引。 可能的未来优化:如果FROM子句中的子查询类似于可施加MERGE算法的视图,改写查询并采用MERGE算法,以便能够使用索引。下述语句包含这类子查询: ~~~ SELECT * FROM (SELECT * FROM t1 WHERE t1.t1_col) AS _t1, t2 WHERE t2.t2_col; ~~~ 该语句可被改写为联合,如下所示: ~~~ SELECT * FROM t1, t2 WHERE t1.t1_col AND t2.t2_col; ~~~ 这类改写具有两个优点: 1.    避免使用那些不能使用索引的临时表。在改写的查询中,优化程序可在t1上使用索引。 2.    优化程序在选择不同的执行计划方面具有更大的自由。例如,将查询改写为联合,那么就允许优化程序首先使用t1或t2。 可能的未来优化:对于没有关联子查询的IN、= ANY、<> ANY、= ALL、以及<> ALL,为结果使用“内存中”哈希处理,或对较大的结果使用具有索引的临时表。例如: ~~~ SELECT a FROM big_table AS bt ~~~ ~~~ WHERE non_key_field IN (SELECT non_key_field FROM table WHERE condition) ~~~ 在该情况下,可创建临时表: ~~~ CREATE TABLE t (key (non_key_field)) ~~~ ~~~ (SELECT non_key_field FROM table WHERE condition) ~~~ 然后,对big_table中的每一行,根据bt.non_key_field,在“t”中进行键查找。 ### I.4. 对视图的限制 视图处理功能概念未优化: ·         不能在视图上创建索引。 ·         对于使用MERGE算法处理的视图,可以使用索引。但是,对于使用临时表算法处理的视图,不能在其基表上利用索引提供的优点(尽管能够在临时表的生成过程中使用索引)。 在视图的FROM子句中不能使用子查询。未来该限制将被放宽。 存在一个一般原则,不能更改某一表并在子查询的相同表内进行选择。请参见[I.3节,“对子查询的限制”](# "I.3. Restrictions on Subqueries")。 如果从表选择了视图并接着从视图进行了选择,同样的原理也适用,如果在子查询中从表选择了视图并使用MERGE算法评估了视图,也同样。例如: ~~~ CREATE VIEW v1 AS ~~~ ~~~ SELECT * FROM t2 WHERE EXISTS (SELECT 1 FROM t1 WHERE t1.a = t2.a); ~~~ ~~~   ~~~ ~~~ UPDATE t1, v2 SET t1.a = 1 WHERE t1.b = v2.b; ~~~ 如果视图是使用临时表评估的,可从视图子查询中的表进行选择,并仍能更改外部查询中的表。在该情况下,视图将被具体化,因此,你实际上不能从子查询的表中进行选择并“同时”更改它(这是你打算强制MySQL使用临时表算法的另一原因,其方法是在视图定义中指定ALGORITHM = TEMPTABLE关键字)。 可以使用DROP TABLE或ALTER TABLE来舍弃或更改视图定义中使用的表(它会是视图失效),而且舍弃或更改操作不会导致告警。但在以后使用视图时会出错。 视图定义是通过特定语句“冻结”的: ·         如果PREPARE预处理的语句引用了视图,以后每次执行语句时看到的视图内容与预处理视图时的内容相同。即使在语句预处理完成之后、在执行语句之前更改了视图定义,情况也同样。例如: ~~~ ·                CREATE VIEW v AS SELECT 1; ~~~ ~~~ ·                PREPARE s FROM 'SELECT * FROM v'; ~~~ ~~~ ·                ALTER VIEW v AS SELECT 2; ~~~ ~~~ ·                EXECUTE s; ~~~ EXECUTE语句返回的结果是1,而不是2。 ·         如果存储子程序中的语句引用了视图,语句所见到的视图内容与首次执行语句时的相同。这意味着,如果语句是以循环方式执行的,进一步的语句迭代见到的视图内容是相同的,即使在循环过程中更改了视图定义也同样。例如: ~~~ ·                CREATE VIEW v AS SELECT 1; ~~~ ~~~ ·                delimiter // ~~~ ~~~ ·                CREATE PROCEDURE p () ~~~ ~~~ ·                BEGIN ~~~ ~~~ ·                  DECLARE i INT DEFAULT 0; ~~~ ~~~ ·                  WHILE i < 5 DO ~~~ ~~~ ·                    SELECT * FROM v; ~~~ ~~~ ·                    SET i = i + 1; ~~~ ~~~ ·                    ALTER VIEW v AS SELECT 2; ~~~ ~~~ ·                  END WHILE; ~~~ ~~~ ·                END; ~~~ ~~~ ·                // ~~~ ~~~ ·                delimiter ; ~~~ ~~~ ·                CALL p(); ~~~ 如果调用了程序p(),每次通过循环时SELECT返回1,即使在循环内更改了视图定义也同样。 关于视图的可更新性,对于视图,其总体目标是,如果任何视图从理论上讲是可更新的,在实际上也应是可更新的。这包括在其定义中有UNION的视图。目前,并非所有理论上可更新的视图均能被更新。最初的视图实施有意采用该方式,为的是尽快地在MySQL中获得有用的可更新视图。很多理论上可更新的视图现已能更新,但限制依然存在: ·         其子查询位于WHERE子句之外任何位置的可更新视图。对于某些其子查询位于SELECT列表中的视图,也是可更新的。 ·         不能使用UPDATE来更新定义为Join的视图的1个以上的基表。 ·         不能使用DELETE来更新定义为Join的视图。 ### I.5. 对XA事务的限制 XA事务支持限于InnoDB存储引擎。 MySQL XA实施是针对外部XA的,其中,MySQL服务器作为资源管理器,而客户端程序作为事务管理器。未实施“内部XA”。这样,就允许MySQL服务器内的单独存储引擎作为RM(资源管理器),而服务器本身作为TM(事务管理器)。处理包含1个以上存储引擎的XA事务时,需要内部XA。内部XA的实施是不完整的,这是因为,它要求存储引擎在表处理程序层面上支持两阶段提交,目前仅对InnoDB实现了该特性。 对于XA START,不支持JOIN和RESUME子句。 对于XA END,不支持SUSPEND [FOR MIGRATE]子句。 在全局事务内,对于每个XA事务,xid值的bqual部分应是不同的,该要求是对当前MySQL XA实施的限制。它不是XA规范的组成部分。 如果XA事务达到PREPARED状态而且MySQL服务器宕机,当服务器重启后,能够继续处理事务。就像原本应当的那样。但是,如果客户端连接中止而服务器继续运行,服务器将回滚任何未完成的XA事务,即使该事务已达到PREPARED状态也同样。它应能提交或回滚PREPARED XA事务,但在不更改二进制日志机制的情况下不能这样。 这是MySQL参考手册的翻译版本,关于MySQL参考手册,请访问[dev.mysql.com](http://dev.mysql.com/doc/mysql/en)。原始参考手册为英文版,与英文版参考手册相比,本翻译版可能不是最新的。