mysql事务与锁以及kotlin的实际用法

mysql中的锁

首先需要介绍一下mysql的锁。一般我们使用InnoDB数据库引擎+行级锁,SQL为:SELECT * FROM table where id = 1 for update;for update为排外锁,锁了后其他session无法再加任何锁,需要释放之后才能操作。行级锁要求where中有唯一索引,如主键或者unique索引。这种锁允许其他session读,但是不可写。大部分情况下,我们不会使用lock tables TABLE write;,因为锁表的开支太大,锁行就足够了。

mysql的锁只在事务中生效。单独执行SELECT * FROM table where id = 1 for update;并不会加锁,需要先执行START TRANSACTION;或者set autocommit=0;后锁才生效,并在commit;或者rollback;后释放锁。否则,即使执行了for update语句,其他session也可以对这行数据随意操作。

mysql事务隔离级别

mysql事务隔离级别分为READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE
mysql默认的隔离级别为REPEATABLE READ,但是阿里云的数据库隔离级别为READ COMMITTED,可以从SELECT @@GLOBAL.transaction_isolation, @@GLOBAL.transaction_read_only;得到当前数据库实际隔离级别。

  • READ UNCOMMITTED 事务中任意改动都会直接生效,可被其他session查到。
  • READ COMMITTED 只有commit的操作才会被其他session查到。
  • REPEATABLE READ 只有commit的操作才会被其他session查到。但是在同一个事务处理中,读取的数据是一致的。如A session开始之后,B session修改数据A为数据B并commit,A session读到的数据依旧是A。
  • SERIALIZABLE 串行操作。在事务中,所有读取操作都会加共享锁,所以必须等写操作完成之后才能读。

从上往下,隔离程度越来越高。

例子如下:

--session 1
START TRANSACTION;
SELECT * FROM table where id = 1 for update;
-- 此时字段a是1
update table set a=100 where id=1;

--session 2
SELECT * FROM table where id = 1;
--不能加for update,否则会被锁。返回值a在READ UNCOMMITTED 下结果为100,其他情况下为1。

--session 1
SELECT * FROM table where id = 1 for update;
--同session可以随便重复锁。返回值a是100

--session 2
START TRANSACTION;
SELECT * FROM table where id = 1;
--READ UNCOMMITTED 下结果为100,其他情况下为1。

--session 1
COMMIT;

--session 2
SELECT * FROM table where id = 1;
--READ UNCOMMITTED和READ COMMITTED结果为100,其他为1,这也是REPEATABLE READ 重复读的含义所在。

kotlin中的具体实践

这里使用的是kotlin+jooq。注:@Transactional注解可以加在interface内的方法上。

    @Transactional
    override fun testRollBackWithT() {
        val res=dsl.selectFrom(Tables.T_TABLE)
                .where(Tables.T_TABLE.ID.eq(1))
                .forUpdate()
                .fetchOne()
        res.time=300
        res.update()
        throw Exception("test")
    }

必须开启Transactional注解,否则无效。此注解在这里的主要作用,是在调用的时候执行set autocommit=0;,调用成功后执行commit;,以及在失败后执行rollback;。不在Transactional作用域内进行的锁是无效的。

此注解本质是用cglib进行切片,处理事务初始化(即set autocommit=0;)等事项。因此如果在类的内部不带注解的方法调用带注解的方法,会导致注解无效。此外,如果嵌套注解,即带注解的A调用带注解的B,整个注解执行过程中任意出错都会导致整体回滚。不管嵌套多少层注解,事务初始化只会执行一次,commit也只会执行一次。任意一个带注解的方法执行失败,都会标注整个事务失败。例子如下:

    @Transactional
    fun test() {
        //此方法带@Transactional注解,不会抛异常
        updateWithout()
        try {
            //此方法带@Transactional注解,会抛异常
            updateWith()
        }catch (e:Exception){
        }
    }

在例子中,updateWith()带@Transactional注解并抛异常了,即使被上层捕获,依然会触发Transactional的回滚标记。虽然由于嵌套导致不会立即回滚,但是在上层运行结束触发commit的时候,会出现异常最终rollback,test方法内部所有数据库操作都会无效,如updateWithout()方法。

你可能感兴趣的:(mysql事务与锁以及kotlin的实际用法)