月度归档:2017年03月

基于数据库的乐观锁

涉及涉及数据库的高并发解决方案一般都会提到乐观锁。

乐观锁:在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

利用数据库实现乐观锁的伪代码:

Connection conn = DriverManager.getConnection(url, user, password);
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
// step 1
int oldVersion = getOldVersion(stmt);

// step 2
// 用这个数据库连接做其他的逻辑

// step 3 可用预编译语句
int i = stmt.executeUpdate(
        "update optimistic_lock set version = " + (oldVersion + 1) + " where version = " + oldVersion);

// step 4
if (i > 0) {
    conn.commit(); // 更新成功表明数据没有被修改,提交事务。
} else {
    conn.rollback(); // 更新失败,数据被修改,回滚。
}

注意事项:

  • 前面伪代码的 step 3 和 4 应在存储过程里完成,防止 step 3 完成后,由于 GC 或网络阻塞导致 step 4 延迟执行,此时 乐观锁的记录被数据库锁定,其他请求要进行更新只能等待,被阻塞。
    可以用存储过程来替代后面的两步,或者整个逻辑都用存储过程实现:
delimiter $$

create procedure sp_update_version (newVersion int, oldVersion int) begin
    update optimistic_lock t set t.version = newVersion where t.version = oldVersion;

    if row_count() > 0 then
        commit;
    else
        rollback;
    end if;
end $$

乐观锁的缺点:

  • 会带来大数量的无效更新请求、事务回滚,给DB造成不必要的额外压力。
  • 无法保证先到先得,后面的请求可能由于并发压力小了反而有可能处理成功。