OptimisticConcurrency乐观并发

12 乐观并发

ODB事务模型(第3.5节,“事务”)保证一致性,只要我们在单个数据库事务中执行与特定应用程序事务对应的所有数据库操作。也就是说,如果我们在数据库事务中加载一个对象并在同一个事务中更新它,那么我们就可以保证在数据库中更新的对象状态与我们已经加载的状态完全相同。换句话说,在这些加载和更新操作之间,其他进程或线程不可能修改数据库中的对象状态。

In this chapter we use the term application transaction to refer to a set of operations on persistent objects that an application needs to perform in order to implement some application-specific functionality. The term database transaction refers to the set of database operations performed between the ODB begin() and commit() calls. Up until now we have treated application transactions and database transactions as essentially the same thing.

在本章中,我们使用应用程序事务这个术语来指代应用程序为了实现某些特定于应用程序的功能而需要执行的一组对持久对象的操作。术语数据库事务指的是ODB begin()和commit()调用之间执行的一组数据库操作。到目前为止,我们将应用程序事务和数据库事务视为本质上相同的事情。

While this model is easy to understand and straightforward to use, it may not be suitable for applications that have long application transactions. The canonical example of such a situation is an application transaction that requires user input between loading an object and updating it. Such an operation may take an arbitrary long time to complete and performing it within a single database transaction will consume database resources as well as prevent other processes/threads from updating the object for too long.

虽然此模型易于理解且易于使用,但它可能不适用于具有较长应用程序事务的应用程序。这种情况的典型例子是一个应用程序事务,在加载对象和更新对象之间需要用户输入。这样的操作可能需要很长时间才能完成,并且在单个数据库事务中执行它将消耗数据库资源,并防止其他进程/线程更新对象的时间过长。

The solution to this problem is to break up the long-lived application transaction into several short-lived database transactions. In our example that would mean loading the object in one database transaction, waiting for user input, and then updating the object in another database transaction. For example:

此问题的解决方案是将长期存在的应用程序事务分解为几个短期存在的数据库事务。在我们的示例中,这意味着在一个数据库事务中加载对象,等待用户输入,然后在另一个数据库事务中更新对象。例如:

unsigned long id = ...;

person p;

{

  transaction t (db.begin ());

  db.load (id, p);

  t.commit ();

}

cerr << "enter age for " << p.first () << " " << p.last () << endl;

unsigned short age;

cin >> age;

p.age (age);

{

  transaction t (db.begin ());

  db.update (p);

  t.commit ();

}

This approach works well if we only have one process/thread that can ever update the object. However, if we have multiple processes/threads modifying the same object, then this approach does not guarantee consistency anymore. Consider what happens in the above example if another process updates the person's last name while we are waiting for the user input. Since we loaded the object before this change occured, our version of the person's data will still have the old name. Once we receive the input from the user, we go ahead and update the object, overwriting both the old age with the new one (correct) and the new name with the old one (incorrect).

如果我们只有一个进程/线程可以更新对象,这种方法会很好地工作。但是,如果我们有多个进程/线程修改同一个对象,那么这种方法就不能保证一致性了。考虑一下,如果在我们等待用户输入时,另一个进程更新了用户的姓,那么上面的示例中会发生什么。由于我们在此更改发生之前加载了对象,所以person数据的版本仍将保留旧名称。一旦我们收到用户的输入,我们继续并更新对象,用新名称覆盖旧名称(正确)和用旧名称覆盖新名称(不正确)。

While there is no way to restore the consistency guarantee in an application transaction that consists of multiple database transactions, ODB provides a mechanism, called optimistic concurrency, that allows applications to detect and potentially recover from such inconsistencies.

虽然无法在包含多个数据库事务的应用程序事务中恢复一致性保证,但ODB提供了一种称为乐观并发的机制,允许应用程序检测这种不一致性并可能从这种不一致性中恢复。

In essence, the optimistic concurrency model detects mismatches between the current object state in the database and the state when it was loaded into the application memory. Such a mismatch would mean that the object was changed by another process or thread. There are several ways to implement such state mismatch detection. Currently, ODB uses object versioning while other methods, such as timestamps, may be supported in the future.

本质上,乐观并发模型检测数据库中当前对象状态与加载到应用程序内存时的状态之间的不匹配。这种不匹配意味着对象被另一个进程或线程更改了。有几种方法可以实现这种状态不匹配检测。目前,ODB使用对象版本控制,而其他方法(如时间戳)可能在将来得到支持。

To declare a persistent class with the optimistic concurrency model we use the optimistic pragma (Section 14.1.5, "optimistic"). We also use the version pragma (Section 14.4.16, "version") to specify which data member will store the object version. For example:

为了使用乐观并发模型声明一个持久类,我们使用optimistic pragma(第14.1.5节,“乐观”)。我们还使用version pragma(章节14.4.16,"version")来指定哪个数据成员将存储对象的版本。例如:

#pragma db object optimistic

class person

{

  ...

  #pragma db version

  unsigned long version_;

};

The version data member is managed by ODB. It is initialized to 1 when the object is made persistent and incremented by 1 with each update. The 0 version value is not used by ODB and the application can use it as a special value, for example, to indicate that the object is transient. Note that for optimistic concurrency to function properly, the application should not modify the version member after making the object persistent or loading it from the database and until deleting the state of this object from the database. To avoid any accidental modifications to the version member, we can declare it const, for example:

版本数据成员由ODB管理。当对象被持久化时,它被初始化为1,并随着每次更新而增加1。ODB不使用0版本值,应用程序可以使用它作为一个特殊值,例如,表示对象是临时的。注意,为了使乐观并发正常运行,应用程序不应该在使对象持久化或从数据库加载它之后修改版本成员,直到将该对象的状态从数据库中删除为止。为了避免对version成员的任何意外修改,可以将其声明为const,例如:

#pragma db object optimistic

class person

{

  ...

  #pragma db version

  const unsigned long version_;

};

When we call the database::update() function (Section 3.10, "Updating Persistent Objects") and pass an object that has an outdated state, the odb::object_changed exception is thrown. At this point the application has two recovery options: it can abort and potentially restart the application transaction or it can reload the new object state from the database, re-apply or merge the changes, and call update() again. Note that aborting an application transaction that performs updates in multiple database transactions may require reverting changes that have already been committed to the database. As a result, this strategy works best if all the updates are performed in the last database transaction of the application transaction. This way the changes can be reverted by simply rolling back this last database transaction.

当我们调用database::update()函数(第3.10节,“更新持久对象”)并传递一个过时状态的对象时,会抛出odb::object_changed异常。此时,应用程序有两个恢复选项:它可以中止并重启应用程序事务,也可以从数据库重新加载新的对象状态,重新应用或合并更改,并再次调用update()。请注意,中止在多个数据库事务中执行更新的应用程序事务可能需要恢复已经提交到数据库的更改。因此,如果所有更新都在应用程序事务的最后一个数据库事务中执行,则此策略的效果最佳。这样,只需回滚最后一个数据库事务,就可以恢复更改。

The following example shows how we can reimplement the above transaction using the second recovery option:

下面的例子展示了我们如何使用第二个恢复选项来重新实现上面的事务:

unsigned long id = ...;

person p;

{

  transaction t (db.begin ());

  db.load (id, p);

  t.commit ();

}

cerr << "enter age for " << p.first () << " " << p.last () << endl;

unsigned short age;

cin >> age;

p.age (age);

{

  transaction t (db.begin ());

  try

  {

    db.update (p);

  }

  catch (const object_changed&)

  {

    db.reload (p);

    p.age (age);

    db.update (p);

  }

  t.commit ();

}

An important point to note in the above code fragment is that the second update() call cannot throw the object_changed exception because we are reloading the state of the object and updating it within the same database transaction.

在上面的代码片段中需要注意的一点是,第二次update()调用不能抛出object_changed异常,因为我们正在同一个数据库事务中重新加载对象的状态并更新它。

Depending on the recovery strategy employed by the application, an application transaction with a failed update can be significantly more expensive than a successful one. As a result, optimistic concurrency works best for situations with low to medium contention levels where the majority of the application transactions complete without update conflicts. This is also the reason why this concurrency model is called optimistic.

根据应用程序所采用的恢复策略,更新失败的应用程序事务可能比成功更新的应用程序事务开销大得多。因此,乐观并发最适合低到中等争用级别的情况,在这种情况下,大多数应用程序事务完成而没有更新冲突。这也是为什么这种并发模型被称为乐观型的原因。

In addition to updates, ODB also performs state mismatch detection when we are deleting an object from the database (Section 3.11, "Deleting Persistent Objects"). To understand why this can be important, consider the following application transaction:

除了更新之外,当我们从数据库中删除对象时,ODB还执行状态不匹配检测(第3.11节,“删除持久对象”)。要理解为什么这很重要,请考虑以下应用程序事务:

unsigned long id = ...;

person p;

{

  transaction t (db.begin ());

  db.load (id, p);

  t.commit ();

}

string answer;

cerr << "age is " << p.age () << ", delete?" << endl;

getline (cin, answer);

if (answer == "yes")

{

  transaction t (db.begin ());

  db.erase (p);

  t.commit ();

}

Consider again what happens if another process or thread updates the object by changing the person's age while we are waiting for the user input. In this case, the user makes the decision based on a certain age while we may delete (or not delete) an object that has a completely different age. Here is how we can fix this problem using optimistic concurrency:

再考虑一下,如果在我们等待用户输入时,另一个进程或线程通过更改人的年龄来更新对象,会发生什么。在这种情况下,用户根据特定的年龄做出决定,而我们可以删除(或不删除)具有完全不同年龄的对象。下面是我们如何使用乐观并发来解决这个问题:

unsigned long id = ...;

person p;

{

  transaction t (db.begin ());

  db.load (id, p);

  t.commit ();

}

string answer;

for (bool done (false); !done; )

{

  if (answer.empty ())

    cerr << "age is " << p.age () << ", delete?" << endl;

  else

    cerr << "age changed to " << p.age () << ", still delete?" << endl;

  getline (cin, answer);

  if (answer == "yes")

  {

    transaction t (db.begin ());

    try

    {

      db.erase (p);

      done = true;

    }

    catch (const object_changed&)

    {

      db.reload (p);

    }

    t.commit ();

  }

  else

    done = true;

}

Note that state mismatch detection is performed only if we delete an object by passing the object instance to the erase() function. If we want to delete an object with the optimistic concurrency model regardless of its state, then we need to use the erase() function that deletes an object given its id, for example:

请注意,状态不匹配检测只有在我们通过将对象实例传递给erase()函数来删除对象时才会执行。如果我们想要删除一个具有乐观并发模型的对象,而不管它的状态如何,那么我们需要使用erase()函数来删除给定id的对象,例如:

{

  transaction t (db.begin ());

  db.erase (p.id ());

  t.commit ();

}

Finally, note that for persistent classes with the optimistic concurrency model both the update() function as well as the erase() function that accepts an object instance as its argument no longer throw the object_not_persistent exception if there is no such object in the database. Instead, this condition is treated as a change of object state and the object_changed exception is thrown instead.

最后,请注意,对于具有乐观并发模型的持久类,update()函数和接受对象实例作为参数的erase()函数不再抛出object_not_persistent异常,如果数据库中没有这样的对象的话。相反,将此条件视为对象状态的更改,并抛出object_changed异常。

For complete sample code that shows how to use optimistic concurrency, refer to the optimistic example in the odb-examples package.