原子读写数据库中字段的最佳实践

时间:2009-10-28 03:51:58

标签: java-ee

我来自Java Desktop Application背景。我可以知道J2EE中的最佳实践是什么,原子读写数据库中的字段。目前,这是我做的

// In Servlet.
synchronized(private_static_final_object)
{
    int counter = read_counter_from_database();
    counter = some_calculation_that_shall_be_done_outside_database(counter);
    write_counter_back_to_database(counter);
}

但是,我怀疑上述方法会一直有效。

正如我的观察那样,如果我同时有几个web请求,我正在使用不同的线程在servlet的单个实例中执行代码。上面的方法应该工作,因为不同的线程web请求都是指同一个“private_static_final_object”

但是,我的猜测是“servlet的单个实例”并不能保证。在一段时间之后,servlet的前一个实例可能会被破坏,并且会创建另一个新的servlet实例。

我在JDO中也遇到过http://code.google.com/appengine/docs/java/datastore/transactions.html。我不确定他们是否会解决问题。

// In Servlet.
Transaction tx = pm.currentTransaction();
tx.begin();
    int counter = read_counter_from_database();  // Line 1
    counter = some_calculation_that_shall_be_done_outside_database(counter);// Line 2                 
    write_counter_back_to_database(counter);     // Line 3
tx.commit();

只有当线程A以原子方式执行第1行到第3行时,代码才能保证,只有线程B可以继续以原子方式执行第1行到第3行吗?

因为我不希望发生以下情况。

  1. 线程数据库中的读取计数器为0
  2. 线程A对计数器0执行计算
  3. 线程B从数据库读取计数器为0
  4. 线程将计数器0(假设结果为42)的写入计算结果发送到数据库
  5. 线程B对计数器0执行计算
  6. 线程B将计数器0(假设结果为42)的计算结果写入数据库
  7. 我希望是什么

    1. 线程数据库中的读取计数器为0
    2. 线程A对计数器0执行计算
    3. 线程将计数器0(假设结果为42)的写入计算结果发送到数据库
    4. 线程B从数据库读取计数器为42
    5. 线程B在计数器42上执行计算
    6. 线程B将计数器42的计算结果(假设结果为55)写入数据库
    7. 谢谢你。

3 个答案:

答案 0 :(得分:3)

此:

Transaction tx = pm.currentTransaction();
tx.begin();

int counter = read_counter_from_database();  // Line 1
counter++;                                   // Line 2
write_counter_back_to_database(counter);     // Line 3

tx.commit();

..不安全。

数据库采用所谓的隔离级别,在提交INSERT / UPDATE时可以读取数据。它使阅读速度更快,但存在过时数据的风险。当你的计数器变量递增时,我本可以提交我的插入 - 你最好冒一个主要或唯一的密钥验证错误,最糟糕的是坏数据。

我的建议是让相应的数据库实用程序处理这些情况,因为它们是安全的。对于Oracle来说,这是一个序列。 SQL Server将其称为IDENTITY; MySQL称之为自动增量...

答案 1 :(得分:0)

为什么不简单地将这两个想法结合起来:

synchronized(private_static_final_object)
{
    Transaction tx = pm.currentTransaction();
    tx.begin();
        int counter = read_counter_from_database();  
        counter++;                                   
        write_counter_back_to_database(counter);    
    tx.commit();
}

您可以在db事务中使用代码和原子性中的关键部分进行线程同步。

单独使用synchronized关键字不会“保护”db transactional atomicity。

另一种选择:

根据 rexem的观察结果,尽管仍然不完美,但最安全的做法是在数据库引擎内的存储过程中的事务中执行此操作。拉取数据,递增数据然后将其保存回来会为错误数据打开太多可能性。

答案 2 :(得分:0)

不确定这是否适用于您的用例,但在数据库级别中可以使用以下内容:

1. begin transaction
2. update counter = counter+1
3. read value
4. commit

当一个线程执行2时,其他线程将在2之前阻塞,直到第一个线程进行提交。

编辑:如果数据库无法进行更新,则必须使用 select for update 语句来锁定其他人执行:

1. begin transaction
2. select counter for update
3. calculate new counter value in application layer
4. update counter on database
5. commit

现在,当另一个线程在2到5之间时,线程将始终在第2步阻塞。整个操作将是原子操作。有关MySQL InnoDB的选择更新语法,请访问: http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html