在Mybatis中使用手动加锁的方式操作数据库

在使用Spring整合Mybatis进行数据库操作时,我们可以通过Spring的注解@Transactional来实现事务,同时可以在注解中对数据库设置隔离级别来进行并发操作数据库时候的控制。

但是对于某些情况,仅仅使用数据库隔离级别无法达到最优的效果,比如两个事务同时对一张表进行操作,其中一个事务对表进行读取,而另一个事务对表进行插入操作,在PostgreSQL,Orecal以及SQL Server中,由于采用的是读已提交的隔离级别,所以当读事务的锁和写事务的锁是不冲突的,这就会导致这两种事务可能会并发交替执行,最终的结果是如果读事务在事务的生命周期中对表进行了多次读取的话,前后两次可能读取到不同的值,因为写事务对数据库的操作只要是经过Commit,就会对读事务可见。将数据库隔离级别设置为串行化可以解决这个问题,但是串行化会导致数据库的并发能力降低。

我们都知道数据库会提供一些加锁的语句来人为对数据库表进行加锁,接下来就尝试在Mybatis中动态对数据库的表进行加锁操作。

首先我们需要设置数据库可以同时执行多条语句,即在配置文件中的数据库url的后面加上allowMultiQueries=true,这样我们就可以在Mybatis的mapper.xml的标签中写入多行SQL语句来执行。

"jdbc:mysql://localhost:3306/xxx?allowMultiQueries=true"

接着我们随意找一个Mybatis的xml文件,在任意一句标签中的SQL前面多加一句“LOCK TABLE users READ”。

<mapper namespace="hello.UserMapper">
    <select id="getUser" parameterType="int" resultType="hello.User">
        LOCK TABLE users READ;
        select * from users where id=#{id};
    select>
mapper>

然后在Console中打开数据库,找到users表,依次执行如下命令

START TRANSACTION;
LOCK TABLES users WRITE;

在这里我们开启了一个事务,在这个事务中首先是获取users表的写锁,只要我们不执行commit或则rollback,这个事务将会一直持有这个锁。

接着回到程序,在程序中我们执行如下代码

    InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
    SqlSession session = sessionFactory.openSession();
    String statement = "hello.UserMapper.getUser";
    User user = session.selectOne(statement, 1);
    System.out.println(user);

观察程序的输出,此时我们可以看到控制台没有输出,同时程序也没有结束,而是处于阻塞状态,此时再回到数据库控制台,输入

COMMIT;

这时可以看到Mybatis程序的控制台输出了结果,因为当持有锁的事务结束之后就会释放锁,这时尝试获取读锁的Mybatis程序就会获取到被释放的锁,于是就可以往下继续执行并最终回去查询到的结果。

通过使用人工加锁的方式可以很好地避免改变数据库隔离级别来防止并发错误,同时大部分数据库都提供了比表锁更加精细的行锁,可以大大提高并发的效率,这些锁都是不能通过简单使用Spring的注解来实现的,同时由于很多数据库的设计思想是写事务和读事务是可以并发执行的,如果想要实现串行化的读写也可以尝试使用人工加锁的方式。

你可能感兴趣的:(Mybatis)