mybatis-mysql-自增id獲取坑

mybatis-mysql-自增id獲取坑

坑場景描述

實際場景可能複雜,簡單還原場景html

環境

  • mybatis 3.2.8
  • tddl 3.1.0.4
  • mysql 5.6.16-log
  • spring 3.1.2.RELEASE

user 字段名稱 | 類型 ---|--- id | int(10) auto_increment name | varchar(50)java

score 字段名稱 | 類型 ---|--- id | int(10) auto_increment user_id | int(10) score | int(3)mysql

mybatis配置

user.mapper 非相關 省略spring

<insert id="insert" parameterType="User" >
  	<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
	SELECT LAST_INSERT_ID() AS id
	</selectKey>
    insert into user (id, name)
    values (#{id,jdbcType=INTEGER}, #{name,jdbcType=CHAR}
      )
  </insert>

score.mapper 非相關 省略sql

<insert id="insert" parameterType="User" >
  	<selectKey resultType="java.lang.Integer" order="AFTER" keyProperty="id">
	SELECT LAST_INSERT_ID() AS id
	</selectKey>
    insert into score (id, user_id,score)
    values (#{id,jdbcType=INTEGER}, #{userId,jdbcType=INTEGER},
    #{score,jdbcType=INTEGER}
      )
  </insert>

需求場景

插入user後須要插入sore須要獲取到user_id,非同一事物中服務器

存在的問題

在線上出現插入user後獲取到id不爲當前插入後的id而爲score表的id(系統存在併發狀況可能存在多個插入同時操做)mybatis

問題定位

LAST_INSERT_ID()定義

mysql5.6官方文檔併發

LAST_INSERT_ID(), LAST_INSERT_ID(expr)

沒有參數, LAST_INSERT_ID()返回一個64位值,表示AUTO_INCREMENT因爲最近執行的INSERT 語句而致使爲列成功插入的第一個自動生成的值 。在此以前,該值具備BIGINT UNSIGNEDMySQL 5.6.9 類型BIGINT(簽名)。LAST_INSERT_ID()若是沒有成功插入行,則該值 保持不變。

使用一個參數, LAST_INSERT_ID()返回MySQL 5.6.9以前的無符號整數,一個有符號的整數。

例如,在插入生成AUTO_INCREMENT值的行以後 ,能夠獲得以下值:

mysql> SELECT LAST_INSERT_ID();
        -> 195
當前執行的語句不影響其值 LAST_INSERT_ID()。假設您AUTO_INCREMENT使用一個語句生成值,而後LAST_INSERT_ID()在多行INSERT語句中引用 ,該行將行插入到具備其自身AUTO_INCREMENT列的表中 。LAST_INSERT_ID()第二個聲明中的價值 將保持穩定; 其第二行和更後一行的值不受早期行插入的影響。(可是,若是混合引用 LAST_INSERT_ID()和 效果未定義。) LAST_INSERT_ID(expr)

若是之前的語句返回錯誤,則值爲 LAST_INSERT_ID()undefined。對於事務表,若是語句因爲錯誤而回滾,則該值將 LAST_INSERT_ID()保持未定義。對於手動 ROLLBACK,LAST_INSERT_ID() 交易前的值不會恢復; 它仍然是在它的位置 ROLLBACK。

在MySQL 5.6.15以前,若是使用複製過濾規則,則此函數未正確複製。(Bug#17234370,Bug#69861)

在存儲的例程(過程或函數)或觸發器LAST_INSERT_ID()的正文內,更改的值與 在這些對象的體外執行的語句相同。存儲的例程或觸發器對其值的影響 LAST_INSERT_ID()由如下語句所看到取決於例程的種類:

若是存儲過程執行更改值的語句,則更改的值LAST_INSERT_ID()將由過程調用後面的語句看到。

對於存儲的功能和更改值的觸發器,當函數或觸發器結束時,該值將被恢復,所以如下語句將不會看到更改的值。

生成的ID在每一個鏈接的基礎上維護在服務器中 。這意味着函數返回給給定客戶端的AUTO_INCREMENT值是爲該客戶端影響AUTO_INCREMENT列的最新語句生成的第一個 值 。即便這些AUTO_INCREMENT值生成了本身的值,也不會受到其餘客戶端的影響 。此行爲確保每一個客戶端能夠檢索本身的ID,而不用擔憂其餘客戶端的活動,而且不須要鎖或事務。

其中app

生成的ID在每一個鏈接的基礎上維護在服務器中 。這意味着函數返回給給定客戶端的AUTO_INCREMENT值是爲該客戶端影響AUTO_INCREMENT列的最新語句生成的第一個 值 。即便這些AUTO_INCREMENT值生成了本身的值,也不會受到其餘客戶端的影響 。此行爲確保每一個客戶端能夠檢索本身的ID,而不用擔憂其餘客戶端的活動,而且不須要鎖或事務

sprig-mybatis

一個insert執行流程函數

SqlSessionTemplate.insert-->DefaultSqlSession.insert-->SimpleExecutor.update-->PreparedStatementHandler.update
其中insert into user 先執行,其後執行 select last_inset id
(1.ps.execute() 2.keyGenerator.processAfter)

image image

經過debug能夠看到經過SpringManagedTransaction.getConnection兩次open一次直接返回一次能夠肯定兩次操做爲同一Connection(非併發環境)

同時模擬insrt後經過mysql工具鏈接插入數據後返回id正確

對比

生成的ID在每一個鏈接的基礎上維護在服務器中 。這意味着函數返回給給定客戶端的AUTO_INCREMENT值是爲該客戶端影響AUTO_INCREMENT列的最新語句生成的第一個 值 。即便這些AUTO_INCREMENT值生成了本身的值,也不會受到其餘客戶端的影響 。此行爲確保每一個客戶端能夠檢索本身的ID,而不用擔憂其餘客戶端的活動,而且不須要鎖或事務

以及

經過debug能夠看到經過SpringManagedTransaction.getConnection兩次open一次直接返回一次能夠肯定兩次操做爲同一Connection

結論

無(對比僅供參考)

解決方案

替換last_inset id 經過插入後再查詢name獲取id

相關文章
相關標籤/搜索