实践数据库锁

悲观锁

数据库隔离级别是读提交的前提下。

1
2
3
4
5
6
7
8
// 强制锁定数据
executeSql("select ... where id = $id for update");
try {
// process business logic
commit();
} catch (Exception e) {
rollback();
}

accounting lock

乐观锁

利用行锁及原子性,根据update的结果(更新行数)来决定是否更新成功。

1
2
3
4
5
6
boolean result = executeSql("update ...");
if (result) {
// process sucessful logic
} else {
// process failure logic
}

版本号

基于版本精准控制更新,会影响并发,适用于并发不高的场景(比如后台配置)

1
update config set num = 1 where id = 123 and version = 2;

状态机

状态变化,严格根据状态图,控制只有指定状态可以变更为指定状态

1
update order set status = 1 where id = 123 and status = 2;

对比如下,可能会导致其他不支持的状态变化

1
update order set status = 1 where id = 123;

余额

1、扣减余额,不支持负数余额

accounting no lock

1
update account_balance set balance = balance - 100 where id = 123 and balance >= 100;

2、如果需要得到扣减后的余额,除了可以使用悲观锁方式,还可以利用事务行锁

数据库隔离级别是读提交的前提下。

1
2
3
4
5
6
7
8
9
10
// begin transaction
boolean result = executeSql("update ...");
if (result) {
    // 因为上面更新的行被当前事务锁定了
    executeSql("select balance, ... where id = $id");
// process sucessful logic
} else {
// process failure logic
}
// commit transaction

3、如果需要得到扣减后的余额,除了可以使用悲观锁方式,还可以借鉴CAS算法原理(结合版本号),例如:

1
2
3
4
5
6
7
8
9
10
do {
    // 查询得到余额和版本号
    boolean available = executeSql("select version, balance, ... where id = $id and balance >= 100");
    if (!available) {
        return 余额不足
    } else {
        // 如果更新失败,则说明版本号不对或者余额不足,重试之
        result = update account_balance set balance = balance - 100, version = version + 1 where id = 123 and version = $version and balance >= 100";
    }
} while(result)

MySQL锁

mysql> select * from information_schema.INNODB_LOCKS

为什么开发人员必须要了解数据库锁?