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