Mybatis 可以在添加数据时插入时候,返回自增主键(mysql),现在分析一下其中的原理,并说明mysql的LAST_INSERT_ID 如何做到线程安全的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
有下列两种方式
1.通过useGeneratedKeys
<insert id="insertXXX" useGeneratedKeys="true" keyProperty="id">
insert into table_name(name)
values(#{name,jdbcType=VARCHAR})
</insert>
2.通过 selectKey和mysql内置函数
<insert id="insertXXX" useGeneratedKeys="false">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
SELECT LAST_INSERT_ID()
</selectKey>
insert into table_name(name)
values(#{name,jdbcType=VARCHAR})
</insert>

这两种原理并不相同。

对于第一种方式,我们知道mybatis只是对JDBC的封装而已,我们看一下JDBC返回自增主键。

1
2
3
String insertsSql = "INSERT INTO tbl_zname (`name`) VALUES (?)";
PreparedStatement preparedStatement = connection.prepareStatement(insertsSql, Statement.RETURN_GENERATED_KEYS); //返回自增主键
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();//自增主键的ResultSet

到这里为止了吗?肯定不是,既然分析原理,那就深入一点。其实返回自增id,是mysql协议里的。我们抓包看一下。

查询报文:单条插入

响应报文:单条插入

可以看到响应报文中,有一条Last INSERT ID,这个字段即为插入的自增主键。

第二种方式,注意 order=”AFTER”,如果是mysql的自增主键则是after,代表数据插入后再查询。如果是Oracle或者非自增主键如UUID,则是before,代表在插入数据前先生成主键,但是此种方法并不常见,因为如果不是数据库生成主键,代码上也可以生成主键,不需要额外的查询。

对于第二种方式,其本质是二个SQL语句复合而成。

很容易理解,先插入数据,再查询主键。但是有一个问题就产生了,如果是并发情况下,插入和查询,是两个步骤,明显不是原子操作,怎么能保证数据的正确性呢?

LAST_INSERT_ID 函数并不是获取数据库最新的id,LAST_INSERT_ID函数返回的是Last_INSERT_ID这个值,实际上这个值记录在mysql连接中,也就是说LAST_INSERT_ID是连接隔离的,而mysql的网络模型,为一个线程对于一个连接,所以每个连接执行insert语句的时候,只是在本线程中记录了当前线程的Last_INSERT_ID,并返回。之后这个连接如果没有新的inert语句,这个值保持不变。所以这就是连接绑定线程,线程绑定变量,相当于线程局部变量,当然没有线程安全问题。

那对于批量添加生成自增id又是怎么样的呢?难道说返回的是一个列表吗?

其实对于批量添加只能用1的方式进行生成自增id,这个也是JDBC自带的功能。原理其实隐藏在第二幅图中。不管对于单条添加还是批量添加,返回的报文是一样的。只是Last_INSERT_ID并不是最后一条插入的id,而是最后一条插入语句【包括批量插入】的第一条插入id,Affected Rows就是插入条数,所有生成的id为[Last_INSERT_ID, Last_INSERT_ID +Affected Rows) 区间所有整数。



Mybatis      mybatis

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!