NDB Cluster 在事务处理方面存在一些限制,包括以下内容:
事务隔离级别。
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
读取也会使用额外的锁定以确保一致性。请参见 第 25.6.8.4 节,“NDB Cluster 备份故障排除”,了解有关 NDB Cluster 事务隔离级别实现如何影响
NDB
数据库备份和还原的信息。交易和 BLOB 或 TEXT 列。
NDBCLUSTER
仅存储 MySQL 可见表中使用 MySQL 的任何BLOB
或TEXT
数据类型的列值的某一部分;其余BLOB
或TEXT
存储在另一个内部表中,该表对 MySQL 不可访问。这会导致两个相关的问题,您在执行SELECT
语句时应注意这些问题,这些语句针对包含这些类型列的表:对于从 NDB Cluster 表中进行的任何
SELECT
:如果SELECT
包含BLOB
或TEXT
列,则READ COMMITTED
事务隔离级别将转换为带有读锁的读取。这样做是为了保证一致性。对于任何使用唯一键查找检索任何使用任何
BLOB
或TEXT
数据类型的列的SELECT
,并且该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 Cluster 不善于处理大型交易;最好执行多个小型交易,每个交易包含几个操作,而不是尝试执行包含大量操作的单个大型交易。除其他因素外,大型交易需要非常大的内存量。因此,许多 MySQL 语句的事务性行为会受到影响,如下面的列表中所述:
TRUNCATE TABLE
在用于NDB
表时不是事务性的。如果TRUNCATE TABLE
无法清空表,则必须重新运行它,直到成功。DELETE FROM
(即使没有WHERE
子句)是 事务性的。对于包含大量行的表,您可能会发现使用多个DELETE FROM ... LIMIT ...
语句来 “分块” 删除操作可以提高性能。如果您的目标是清空表,则您可能希望使用TRUNCATE TABLE
而不是使用它。ALTER TABLE 和交易。 当复制
NDB
表作为ALTER TABLE
的一部分时,复制的创建是非事务性的。(在任何情况下,当复制被删除时,此操作都会回滚。)
事务和 COUNT() 函数。 当使用 NDB Cluster 复制时,无法保证副本上
COUNT()
函数的事务一致性。 换句话说,当在源上执行一系列语句(INSERT
、DELETE
或两者)在单个事务中更改表中的行数时,在副本上执行SELECT COUNT(*) FROM
查询可能会产生中间结果。 这是因为table
SELECT COUNT(...)
可能执行脏读,并且不是NDB
存储引擎中的错误。(有关更多信息,请参见 Bug #31321。)