1.4.2.3 操作

AnNdbTransaction由一系列操作组成,每个操作都由NdbOperationNdbScanOperationNdbIndexOperationNdbIndexScanOperation(即NdbOperation其子类的或其中之一)的一个实例表示。

有关 NDB Cluster 访问操作类型的一般信息, 请参阅第 1.4.2.3.1 节,“NDB 访问类型” 。

1.4.2.3.1 NDB 访问类型

数据节点进程有许多简单的构造,用于访问 NDB Cluster 中的数据。我们已经创建了一个非常简单的基准来检查每一个的性能。

访问方式有四种:

  • 主键访问。  这是通过其主键访问记录。在最简单的情况下,一次只访问一个记录,这意味着设置多个 TCP/IP 消息的全部成本和上下文切换的多个成本都由这个单个请求承担。在一批发送多个主键访问的情况下,这些访问共享设置必要的 TCP/IP 消息和上下文切换的成本。如果 TCP/IP 消息用于不同的目的地,则需要设置额外的 TCP/IP 消息。

  • 唯一的密钥访问。  唯一键访问类似于主键访问,不同之处在于唯一键访问是作为对索引表的读取执行的,然后是对表的主键访问。但是MySQL Server只发出一个请求,索引表的读取由数据节点处理。此类请求也受益于批处理。

  • 全表扫描。  当不存在用于查找表的索引时,将执行全表扫描。这作为单个请求发送到ndbd进程,然后将表扫描分成一组对所有NDB数据节点进程的并行扫描。

  • 使用有序索引进行范围扫描。  当使用有序索引时,它以与全表扫描相同的方式执行扫描,只是它只扫描那些在 MySQL 服务器(SQL 节点)传输的查询所使用的范围内的记录。当所有绑定的索引属性都包含分区键中的所有属性时,将并行扫描所有分区。

1.4.2.3.2 单行操作

使用 NdbTransaction::getNdbOperation() 或 NdbTransaction::getNdbIndexOperation() 创建操作后,它在以下三个步骤中定义:

  1. 使用 指定标准操作类型 NdbOperation::readTuple()

  2. 使用 指定搜索条件 NdbOperation::equal()

  3. 使用 指定属性操作 NdbOperation::getValue()

这里有两个简单的例子来说明这个过程。为了简洁起见,我们省略了错误处理。

第一个示例使用 NdbOperation

// 1. Retrieve table object
myTable= myDict->getTable("MYTABLENAME");

// 2. Create an NdbOperation on this table
myOperation= myTransaction->getNdbOperation(myTable);

// 3. Define the operation's type and lock mode
myOperation->readTuple(NdbOperation::LM_Read);

// 4. Specify search conditions
myOperation->equal("ATTR1", i);

// 5. Perform attribute retrieval
myRecAttr= myOperation->getValue("ATTR2", NULL);

有关此类的其他示例,请参阅 第 2.5.2 节,“使用同步事务的 NDB API 示例”

第二个例子使用了一个 NdbIndexOperation

// 1. Retrieve index object
myIndex= myDict->getIndex("MYINDEX", "MYTABLENAME");

// 2. Create
myOperation= myTransaction->getNdbIndexOperation(myIndex);

// 3. Define type of operation and lock mode
myOperation->readTuple(NdbOperation::LM_Read);

// 4. Specify Search Conditions
myOperation->equal("ATTR1", i);

// 5. Attribute Actions
myRecAttr = myOperation->getValue("ATTR2", NULL);

第二种类型的另一个示例可以在 第 2.5.6 节“NDB API 示例:在扫描中使用二级索引”中找到。

我们现在更详细地讨论创建和使用同步事务所涉及的每个步骤。

  1. 定义单行操作类型。  支持以下操作类型:

    所有这些操作都对唯一的元组键进行操作。当NdbIndexOperation 使用时,这些操作中的每一个都对定义的唯一哈希索引进行操作。

    笔记

    如果要在同一事务中定义多个操作,则需要为每个操作调用 NdbTransaction::getNdbOperation()NdbTransaction::getNdbIndexOperation()

  2. 指定搜索条件。  搜索条件用于选择元组。使用 设置搜索条件 NdbOperation::equal()

  3. 指定属性操作。  接下来,有必要确定应该读取或更新哪些属性。重要的是要记住:

    • 删除既不能读取也不能设置值,而只能删除它们。

    • 读取只能读取值。

    • 更新只能设置值。通常属性由名称标识,但也可以使用属性的标识来确定属性。

    NdbOperation::getValue() 返回NdbRecAttr 包含读取值的对象。要获得实际值,可以使用以下两种方法之一;该应用程序可以

    调用NdbRecAttr时释放对象 。Ndb::closeTransaction()因此,应用程序无法在对 的任何后续调用之后引用此对象 Ndb::closeTransaction()NdbRecAttr在调用之前 尝试从对象读取数据会 NdbTransaction::execute() 产生未定义的结果。

1.4.2.3.3 扫描操作

扫描大致相当于 SQL 游标,提供了一种执行高速行处理的方法。可以对表(使用 NdbScanOperation)或有序索引(通过 ) 执行扫描NdbIndexScanOperation

扫描操作具有以下特点:

  • 它们可以执行共享、独占或脏读操作。

  • 他们可能会处理多行。

  • 它们可用于更新或删除多行。

  • 它们可以在多个节点上并行运行。

使用 NdbTransaction::getNdbScanOperation() or 创建操作后NdbTransaction::getNdbIndexScanOperation(),执行如下:

  1. 定义标准操作类型,使用 NdbScanOperation::readTuples().

    笔记

    有关死锁的更多信息,请参见NdbScanOperation::readTuples(),这些死锁可能在使用独占锁执行同时、相同的扫描时发生。

  2. NdbScanFilter使用、 NdbIndexScanOperation::setBound()或两者 指定搜索条件 。

  3. 使用 指定属性操作 NdbOperation::getValue()

  4. 使用 执行交易 NdbTransaction::execute()

  5. 通过连续调用 遍历结果集 NdbScanOperation::nextResult()

这里有两个简单的例子来说明这个过程。再一次,为了使事情相对简短,我们放弃了任何错误处理。

第一个示例使用以下命令执行表扫描 NdbScanOperation

// 1. Retrieve a table object
myTable= myDict->getTable("MYTABLENAME");

// 2. Create a scan operation (NdbScanOperation) on this table
myOperation= myTransaction->getNdbScanOperation(myTable);

// 3. Define the operation's type and lock mode
myOperation->readTuples(NdbOperation::LM_Read);

// 4. Specify search conditions
NdbScanFilter sf(myOperation);
sf.begin(NdbScanFilter::OR);
sf.eq(0, i);   // Return rows with column 0 equal to i or
sf.eq(1, i+1); // column 1 equal to (i+1)
sf.end();

// 5. Retrieve attributes
myRecAttr= myOperation->getValue("ATTR2", NULL);

第二个示例使用 an NdbIndexScanOperation执行索引扫描:

// 1. Retrieve index object
myIndex= myDict->getIndex("MYORDEREDINDEX", "MYTABLENAME");

// 2. Create an operation (NdbIndexScanOperation object)
myOperation= myTransaction->getNdbIndexScanOperation(myIndex);

// 3. Define type of operation and lock mode
myOperation->readTuples(NdbOperation::LM_Read);

// 4. Specify search conditions
// All rows with ATTR1 between i and (i+1)
myOperation->setBound("ATTR1", NdbIndexScanOperation::BoundGE, i);
myOperation->setBound("ATTR1", NdbIndexScanOperation::BoundLE, i+1);

// 5. Retrieve attributes
myRecAttr = MyOperation->getValue("ATTR2", NULL);

执行扫描所需的每个步骤的一些额外讨论如下:

  1. 定义扫描操作类型。 NdbScanOperation::readTuples() 请务必记住,每个扫描操作 (或 ) 仅支持单个操作NdbIndexScanOperation::readTuples()

    笔记

    如果你想在同一个事务中定义多个扫描操作,那么你需要 为每个操作 单独调用NdbTransaction::getNdbScanOperation() 或 。NdbTransaction::getNdbIndexScanOperation()

  2. 指定搜索条件。  搜索条件用于选择元组。如果没有指定搜索条件,扫描将返回表中的所有行。搜索条件可以是一个 NdbScanFilter(可以同时用于 NdbScanOperationNdbIndexScanOperation)或边界(只能用于索引扫描 - 请参阅 NdbIndexScanOperation::setBound())。索引扫描可以同时使用 NdbScanFilter和 边界。

    笔记

    使用 NdbScanFilter 时,检查每一行,无论它是否实际返回。但是,当使用边界时,只会检查边界内的行。

  3. 指定属性操作。  接下来,有必要定义应该读取哪些属性。与事务属性一样,扫描属性由名称定义,但也可以使用属性的身份来定义属性。正如本文档其他地方所讨论的(参见 第 1.4.2.2 节“同步事务”NdbOperation::getValue() ),读取的值 作为 NdbRecAttr对象 由方法返回 。

1.4.2.3.4 使用扫描更新或删除行

扫描也可用于更新或删除行。这是按如下方式执行的:

  1. 使用排他锁扫描 NdbOperation::LM_Exclusive

  2. 当遍历结果集时:)对于每一行,可选择调用 NdbScanOperation::updateCurrentTuple() or NdbScanOperation::deleteCurrentTuple()

  3. 如果执行 NdbScanOperation::updateCurrentTuple():) 只需使用 即可为记录设置新值 NdbOperation::setValue()NdbOperation::equal() 在这种情况下不应调用,因为主键是从扫描中检索到的。

重要的

NdbTransaction::execute() 与单行操作一样, 在进行下一次调用之前不会实际执行更新或删除 。NdbTransaction::execute() 也必须在释放任何锁之前调用;有关详细信息,请参阅 第 1.4.2.3.5 节,“使用扫描进行锁定处理”

特定于索引扫描的功能。  执行索引扫描时,可以使用 仅扫描表的一个子集 NdbIndexScanOperation::setBound()。此外,结果集可以使用升序或降序排序 NdbIndexScanOperation::readTuples()。请注意,默认情况下返回的行是无序的,除非 sorted设置为 true

同样重要的是要注意,当使用 NdbIndexScanOperation::BoundEQ(参见 NdbIndexScanOperation::BoundType)分区键时,实际上只会扫描包含行的片段。最后,在执行排序扫描时,作为 NdbIndexScanOperation::readTuples() 方法parallel参数传递的任何值都将被忽略,取而代之的是使用最大并行度。换句话说,在这种情况下,可以同时并并行地扫描所有可能扫描的片段。

1.4.2.3.5 扫描锁定处理

对表或索引执行扫描有可能返回大量记录;但是,Ndb 一次只锁定每个片段的预定行数。每个片段锁定的行数由传递给 的批处理参数控制 NdbScanOperation::readTuples()

为了使应用程序能够处理锁的释放方式, NdbScanOperation::nextResult() 有一个布尔参数 fetchAllowed。如果 NdbScanOperation::nextResult() 调用fetchAllowed等于false,则函数调用不会释放任何锁。否则可能会释放当前批次的锁。

下一个示例显示了一个以高效方式处理锁的扫描删除。为了简洁起见,我们省略了错误处理。

int check;

// Outer loop for each batch of rows
while((check = MyScanOperation->nextResult(true)) == 0)
{
  do
  {
    // Inner loop for each row within the batch
    MyScanOperation->deleteCurrentTuple();
  }
  while((check = MyScanOperation->nextResult(false)) == 0);

  // When there are no more rows in the batch, execute all defined deletes
  MyTransaction->execute(NoCommit);
}

有关扫描的更完整示例,请参阅 第 2.5.5 节,“NDB API 基本扫描示例”

1.4.2.3.6 错误处理

在定义构成事务的操作时或在实际执行事务时都可能发生错误。捕获和处理任何一种错误都需要测试 返回的值 NdbTransaction::execute(),然后,如果指示错误(即,如果此值等于-1),则使用以下两种方法来识别错误的类型和位置:

这个简短的示例说明了如何检测错误并使用这两种方法来识别错误:

theTransaction = theNdb->startTransaction();
theOperation = theTransaction->getNdbOperation("TEST_TABLE");
if(theOperation == NULL)
  goto error;

theOperation->readTuple(NdbOperation::LM_Read);
theOperation->setValue("ATTR_1", at1);
theOperation->setValue("ATTR_2", at1);  //  Error occurs here
theOperation->setValue("ATTR_3", at1);
theOperation->setValue("ATTR_4", at1);

if(theTransaction->execute(Commit) == -1)
{
  errorLine = theTransaction->getNdbErrorLine();
  errorOperation = theTransaction->getNdbErrorOperation();
}

这里errorLine3,因为错误发生在调用 NdbOperation对象的第三个方法中(在本例中为theOperation)。如果 的结果 NdbTransaction::getNdbErrorLine()0,则在执行操作时发生错误。在此示例中, errorOperation是指向对象的指针 theOperation。该 NdbTransaction::getNdbError() 方法返回一个NdbError 提供有关错误信息的对象。

笔记

发生错误时, 事务不会自动关闭。您必须致电 Ndb::closeTransaction()NdbTransaction::close() 关闭交易。

请参阅Ndb::closeTransaction()NdbTransaction::close()

一种处理事务失败(即报告错误时)的推荐方法如下所示:

  1. NdbTransaction::execute() 通过使用参数的特殊 ExecType 值 调用来回滚事务 type

    有关如何完成此操作的更多信息, 请参见NdbTransaction::execute()NdbTransaction::ExecType 。

  2. 通过调用关闭事务 NdbTransaction::close()

  3. 如果错误是暂时的,请尝试重新启动事务。

当一个事务包含同时执行的多个操作时,可能会发生多个错误。在这种情况下,应用程序必须遍历所有操作并查询它们的每个 NdbError对象以找出真正发生的事情。

重要的

即使提交被报告为成功,也可能会发生错误。为了处理这种情况,NDB API 提供了一种额外的 NdbTransaction::commitStatus() 方法来检查事务的提交状态。

请参阅NdbTransaction::commitStatus()