1.4.2.3 操作

一个 NdbTransaction 包含一系列操作,每个操作都由一个 NdbOperationNdbScanOperationNdbIndexOperationNdbIndexScanOperation 实例表示(即 NdbOperation 或其子类之一)。

有关 NDB 集群访问操作类型的常规信息,请参阅 第 1.4.2.3.1 节,“NDB 访问类型”

1.4.2.3.1 NDB 访问类型

数据节点进程有一些简单的构造,用于访问 NDB 集群中的数据。我们创建了一个非常简单的基准测试来检查每个构造的性能。

有四种访问方法

  • 主键访问。  这是通过主键访问记录。在最简单的情况下,一次只访问一条记录,这意味着此单一请求承担了建立多个 TCP/IP 消息的全部成本以及多个上下文切换的成本。在一次批处理中发送多个主键访问的情况下,这些访问共享建立必要的 TCP/IP 消息和上下文切换的成本。如果 TCP/IP 消息的目标不同,则需要建立其他 TCP/IP 消息。

  • 唯一键访问。  唯一键访问类似于主键访问,不同之处在于唯一键访问被执行为对索引表的读取,然后是对表的读取。主键访问。但是,MySQL 服务器只发送一个请求,索引表的读取由数据节点处理。此类请求也从批处理中获益。

  • 全表扫描。  当表上不存在用于查找的索引时,将执行全表扫描。这将作为单个请求发送到 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 对象。若要获取实际值,可以使用两种方法之一;应用程序可以

    当调用 Ndb::closeTransaction() 时,将释放 NdbRecAttr 对象。因此,应用程序在调用任何后续的 Ndb::closeTransaction() 之后不能引用此对象。尝试在调用 NdbTransaction::execute() 之前从 NdbRecAttr 对象读取数据会导致结果不确定。

1.4.2.3.3 扫描操作

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

扫描操作具有以下特点

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

  • 它们可以潜在地处理多行。

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

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

使用 NdbTransaction::getNdbScanOperation()NdbTransaction::getNdbIndexScanOperation() 创建操作后,它将按照以下步骤执行

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

    注意

    有关在执行带有独占锁的并发、相同扫描时可能发生的死锁的更多信息,请参阅 NdbScanOperation::readTuples()

  2. 使用 NdbScanFilterNdbIndexScanOperation::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);

第二个示例使用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()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. 通过使用ExecType值的特定值作为type参数,调用NdbTransaction::execute()来回滚事务。

    请参见NdbTransaction::execute()NdbTransaction::ExecType,了解有关如何执行此操作的更多信息。

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

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

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

重要

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

请参见NdbTransaction::commitStatus()