NDB 集群在事务处理方面存在一些限制。这些限制包括以下几点:
-
事务隔离级别。
NDBCLUSTER
存储引擎仅支持READ COMMITTED
事务隔离级别。(例如,InnoDB
支持READ COMMITTED
、READ UNCOMMITTED
、REPEATABLE READ
和SERIALIZABLE
。)请注意,NDB
在每行基础上实现READ COMMITTED
;当读取请求到达存储该行的数据节点时,返回的是该行的最后提交版本。未提交的数据永远不会被返回,但当一个事务修改多行同时与读取同一行的事务并发时,读取事务可能会观察到不同的行的“before”值、“after”值或两者,因为读取请求可以在其他事务提交之前或之后被处理。
要确保某个事务只读取“before”值或“after”值,可以使用
SELECT ... LOCK IN SHARE MODE
来施加行锁。在这种情况下,锁将被持有直到拥有事务被提交。使用行锁也可能会导致以下问题:-
锁等待超时错误的频率增加,降低并发性
-
事务处理开销增加,因为读取需要提交阶段
-
可能会耗尽可用的并发锁数量,该数量由
MaxNoOfConcurrentOperations
限定
NDB
使用READ COMMITTED
对所有读取操作,除非使用了LOCK IN SHARE MODE
或FOR UPDATE
修饰符。LOCK IN SHARE MODE
导致共享行锁被使用;FOR UPDATE
导致独占行锁被使用。唯一键读取自动升级到独占锁以确保自洽读取;BLOB
读取也使用额外的锁以确保一致性。请参阅 第 25.6.8.4 节,“NDB 集群备份故障排除”,以了解 NDB 集群的事务隔离级别实现如何影响
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 表。例如,考虑表
t
由以下CREATE TABLE
语句定义: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
表中的行被更新时,隐藏索引表将被事务中的排他锁所锁定。这意味着任何读操作都必须等待更新完成,即使事务级别为READ COMMITTED
。一种绕过可能阻塞读取的解决方法是强制 SQL 节点忽略唯一索引,而使用
IGNORE INDEX
索引提示作为SELECT
语句的一部分(见 第 10.9.4 节,“索引提示”)。因为 MySQL 服务器为每个在NDB
中创建的唯一索引创建了一个有序索引,这允许有序索引被读取,而避免了唯一索引访问锁定。结果读取与提交读取一致,返回读取时的最后提交值。通过有序索引读取使用集群中的资源较少,可能具有较高的延迟。
也可以通过查询范围而不是唯一值来避免使用唯一索引。
-
回滚. 没有部分事务,也没有部分回滚事务。重复键或类似错误将导致整个事务回滚。
这种行为不同于其他事务存储引擎,如
InnoDB
,它可能回滚单个语句。 -
事务和内存使用情况。 如本章其他地方所提到的,NDB Cluster 不善于处理大事务;它更适合执行多个小事务,每个事务包含少量操作,而不是尝试执行一个大事务,包含许多操作。除其他考虑因素外,大事务需要非常大的内存量。因此,MySQL 语句的交易行为受到以下列表的影响:
-
TRUNCATE TABLE
在NDB
表上不是事务性的。如果TRUNCATE TABLE
无法清空表,则必须重新运行直到成功。 -
DELETE FROM
(即使没有WHERE
子句)是 事务性的。对于包含许多行的表,您可能会发现,使用多个DELETE FROM ... LIMIT ...
语句来“分块”删除操作可以提高性能。如果您的目标是清空表,则可能想使用TRUNCATE TABLE
instead。 -
ALTER TABLE 和事务。 在将
NDB
表复制到ALTER TABLE
中时,创建副本的操作不是事务性的。(无论如何,这个操作将在副本被删除时回滚。)
-
-
事务和 COUNT() 函数。 使用 NDB Cluster 复制时,无法保证
COUNT()
函数在副本上的事务一致性。换言之,在源上执行一系列语句(INSERT
、DELETE
或两者)以在单个事务中更改表中的行数时,在副本上执行SELECT COUNT(*) FROM
查询可能会产生中间结果。这是因为table
SELECT COUNT(...)
可能执行脏读,并不是NDB
存储引擎的 bug。(参见 Bug #31321 以获取更多信息。)