NDB 集群在处理事务方面存在一些限制。 这些限制包括以下内容:
事务隔离级别。
NDBCLUSTER
存储引擎仅支持READ COMMITTED
事务隔离级别。 (例如,InnoDB
支持READ COMMITTED
、READ UNCOMMITTED
、REPEATABLE READ
和SERIALIZABLE
。)您应该记住,NDB
是在每行的基础上实现READ COMMITTED
的;当读取请求到达存储该行的数据库节点时,返回的是该行在该时间点的最后提交版本。未提交的数据永远不会返回,但是当修改多行的 事务与读取相同行的 事务并发提交时,执行读取的 事务可以观察到这些行中不同行的 “之前” 值、“之后” 值或两者兼有,这是因为给定的行读取请求可以在另一个 事务提交之前或之后处理。
要确保给定 事务仅读取之前或之后的值,可以使用
SELECT ... LOCK IN SHARE MODE
来施加行锁。 在这种情况下,锁会一直保持到拥有锁的 事务提交。 使用行锁还会导致以下问题:锁等待超时错误的频率增加,并发性降低
由于读取需要提交阶段,因此 事务处理开销增加
可能耗尽可用的并发锁数量,该数量受
MaxNoOfConcurrentOperations
限制
NDB
对所有读取使用READ COMMITTED
,除非使用了LOCK IN SHARE MODE
或FOR UPDATE
等修饰符。LOCK IN SHARE MODE
会导致使用共享行锁;FOR UPDATE
会导致使用独占行锁。 唯一键读取的锁会被NDB
自动升级,以确保自洽读取;BLOB
读取也采用额外的锁定来确保一致性。有关 NDB 集群的事务隔离级别实现如何影响
NDB
数据库的备份和恢复的信息,请参见 第 25.6.8.4 节 “NDB 集群备份故障排除”。事务与 BLOB 或 TEXT 列。
NDBCLUSTER
仅将使用 MySQL 的任何BLOB
或TEXT
数据类型的列值的一部分存储在 MySQL 可见的表中;BLOB
或TEXT
的其余部分存储在 MySQL 无法访问的单独内部表中。 这会导致两个相关问题,在对包含这些类型列的表执行SELECT
语句时,您应该注意这两个问题:对于从 NDB 集群表执行的任何
SELECT
:如果SELECT
包含BLOB
或TEXT
列,则READ COMMITTED
事务隔离级别将转换为使用读锁进行读取。 这样做是为了保证一致性。对于任何使用唯一键查找来检索使用任何
BLOB
或TEXT
数据类型的任何列并在 事务中执行的SELECT
,在 事务期间(即,直到 事务提交或中止),该表上都会持有共享读锁。对于使用索引或表扫描的查询,即使针对具有
BLOB
或TEXT
列的NDB
表的查询,也不会出现此问题。例如,考虑由以下
CREATE TABLE
语句定义的表t
:CREATE TABLE t ( a INT NOT NULL AUTO_INCREMENT PRIMARY KEY, b INT NOT NULL, c INT NOT NULL, d TEXT, INDEX i(b), UNIQUE KEY u(c) ) ENGINE = NDB,
以下对
t
的查询会导致共享读锁,因为它使用唯一键查找:SELECT * FROM t WHERE c = 1;
但是,此处显示的四个查询都不会导致共享读锁:
SELECT * FROM t WHERE b = 1; SELECT * FROM t WHERE d = '1'; SELECT * FROM t; SELECT b,c WHERE a = 1;
这是因为,在这四个查询中,第一个使用索引扫描,第二个和第三个使用表扫描,而第四个虽然使用主键查找,但不检索任何
BLOB
或TEXT
列的值。您可以通过避免使用检索
BLOB
或TEXT
列的唯一键查找的查询来帮助最大限度地减少共享读锁问题,或者,在无法避免此类查询的情况下,尽快提交之后的事务。
唯一键查找和事务隔离。 唯一索引是在
NDB
中使用内部维护的隐藏索引表实现的。 当使用唯一索引访问用户创建的NDB
表时,首先会读取隐藏索引表以查找主键,然后使用该主键读取用户创建的表。 为了避免在此双重读取操作期间修改索引,隐藏索引表中找到的行会被锁定。 当更新用户创建的NDB
表中唯一索引引用的行时,执行更新的 事务会对隐藏索引表进行独占锁定。 这意味着对同一个(用户创建的)NDB
表的任何读取操作都必须等待更新完成。 即使读取操作的事务级别为READ COMMITTED
,也是如此。一种可以用来绕过潜在阻塞读取的解决方法是强制 SQL 节点在执行读取时忽略唯一索引。 这可以通过在读取表的
SELECT
语句中使用IGNORE INDEX
索引提示来完成(请参见 第 10.9.4 节 “索引提示”)。 因为 MySQL 服务器会为NDB
中创建的每个唯一索引创建一个影子有序索引,所以这可以让有序索引被读取,并避免唯一索引访问锁定。 生成的读取与通过主键进行的提交读取一样一致,返回读取行时最后提交的值。通过有序索引读取对集群中的资源使用效率较低,并且可能会导致更高的延迟。
还可以通过查询范围而不是唯一值来避免使用唯一索引进行访问。
回滚。 没有部分事务,也没有事务的部分回滚。 重复键或类似错误会导致整个 事务回滚。
此行为与其他事务性存储引擎(例如
InnoDB
)的行为不同,后者可能会回滚单个语句。事务和内存使用。 正如本章其他地方所述,NDB 集群不能很好地处理大型 事务;最好执行多个小 事务(每个 事务只有几个操作),而不是尝试执行包含大量操作的单个大型 事务。 除其他考虑因素外,大型 事务需要非常大的内存。 因此,许多 MySQL 语句的事务行为会受到影响,如下面的列表所述:
在
NDB
表上使用时,TRUNCATE TABLE
不是事务性的。 如果TRUNCATE TABLE
未能清空表,则必须重新运行,直到成功为止。DELETE FROM
(即使没有WHERE
子句)是 事务性的。 对于包含大量行的表,您可能会发现使用多条DELETE FROM ... LIMIT ...
语句来“分块”删除操作可以提高性能。 如果您的目标是清空表,则您可能希望改用TRUNCATE TABLE
。ALTER TABLE 和事务。 当复制
NDB
表作为ALTER TABLE
的一部分时,副本的创建是非事务性的。 (在任何情况下,删除副本时,此操作都会回滚。)
事务和 COUNT() 函数。 使用 NDB 集群复制时,无法保证副本上
COUNT()
函数的事务一致性。换句话说,当在源上执行一系列语句(INSERT
、DELETE
或两者兼有)以在单个事务中更改表中的行数时,在副本上执行SELECT COUNT(*) FROM
查询可能会产生中间结果。这是因为table
SELECT COUNT(...)
可能会执行脏读,并且不是NDB
存储引擎中的错误。(有关更多信息,请参见错误 #31321。)