<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                **測試版本:**MySQL5.6.23 **測試表:** ~~~ create table t1 (a int auto_increment primary key, b int, c int, unique key (b));并發執行SQL: replace into t1(b,c) values (2,3) //使用腳本,超過3個會話 ~~~ **背景** Replace into操作可以算是比較常用的操作類型之一,當我們不確定即將插入的記錄是否存在唯一性沖突時,可以通過Replace into的方式讓MySQL自動處理:當存在沖突時,會把舊記錄替換成新的記錄。 我們先來理一下一條簡單的replace into操作(如上例所示)的主要流程包括哪些。 **Step 1\. 正常的插入邏輯** 首先插入聚集索引記錄,在上例中a列為自增列,由于未顯式指定自增值,每次Insert前都會生成一個不沖突的新值。 隨后插入二級索引b,由于其是唯一索引,在檢查duplicate key時,為其加上類型為LOCK_X的記錄鎖。 Tips:對于普通的INSERT操作,當需要檢查duplicate key時,加LOCK_S鎖,而對于Replace into 或者 INSERT..ON DUPLICATE操作,則加LOCK_X記錄鎖。 當UK記錄已經存在時,返回錯誤DB_DUPLICATE_KEY。 **Step 2\. 處理錯誤** 由于檢測到duplicate key,因此第一步插入的聚集索引記錄需要被回滾掉(row_undo_ins)。 **Step 3\. 轉換操作** 從InnoDB層失敗返回到Server層后,收到duplicate key錯誤,首先檢索唯一鍵沖突的索引,并對沖突的索引記錄(及聚集索引記錄)加鎖。 隨后確認轉換模式以解決沖突: * 如果發生uk沖突的索引是最后一個唯一索引、沒有外鍵引用、且不存在delete trigger時,使用UPDATE ROW的方式來解決沖突; * 否則,使用DELETE ROW + INSERT ROW的方式解決沖突。 **Step 4\. 更新記錄** 對于聚集索引,由于PK列發生變化,采用delete + insert 聚集索引記錄的方式更新。 對于二級uk索引,同樣采用標記刪除 + 插入的方式。 我們知道,在嘗試插入一條記錄時,如果插入位置的下一條記錄上存在記錄鎖,那么在插入時,當前session需要對其加插入意向鎖,具體類型為LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION。這也是導致死鎖的關鍵點之一。 **是否能保證自增列的有序性?** 默認情況下,參數innodb_autoinc_lock_mode的值為1,因此只在分配自增列時互斥(如果我們將其設為0的話,就會產生AUTO_INC類型的表級鎖)。當分配完自增列值后,我們并不知道并發的replace into的順序。 **死鎖分析** 回到死鎖線程分析,從死鎖日志我們大致可以推斷出如下序列(本例中死鎖的heap no為5): * Session 1 執行到Step4, 準備更新二級Uk索引,因此持有uk上heap no 為5的X 行鎖和PK上的X行鎖; * Session 2 檢查到uk沖突,需要加X行鎖; * Session 1 在標記刪除記錄后,嘗試插入新的uk記錄,發現預插入點的下一條記錄(heap no =5) 上有鎖請求,因此嘗試加插入意向X鎖,產生鎖升級, 死鎖路徑:Session1 => Session 2 => Session1。 到這里其實問題已經很明顯了,我們考慮如下場景:假設當前表內數據為: ~~~ root@sb1 08:57:41>select * from t1; +---------+------+------+ | a | b | c | +---------+------+------+ | 2100612 | 2 | 3 | +---------+------+------+ 1 row in set (0.00 sec) ~~~ 由于不能保證自增列被更新的有序性,我們假定有三個并發的會話,并假定表上只有一條記錄。 session 1獲得自增列值為2100619, session 2 獲得的自增列值為2100614, session 3獲得的自增列值為2100616。 Session 1: replace into t1 values (2100619, 2, 3); // uk索引上記錄(2, 2100612)被標記刪除,同時插入新記錄(2, 2100619) * Purge線程啟動,(2, 2100612)被物理刪除,Page上只剩下唯一的物理記錄(2, 2100619)。 Session 2: replace into t1 values (2100614, 2, 3); 這里我們使用gdb的non-stop模式,使其斷在row_update_for_mysql函數(insert嘗試失敗后,會轉換成update),此時session2持有(2, 2100619) 的X鎖。 ~~~ Tips:我們可以通過如下命令使用gdb的non-stop模式: 1\. 以gdb啟動mysqld 2\. 設置: set target-async 1 set pagination off set non-stop on 3\. 設置函數斷點,然后run ~~~ Session 3: replace into t1 values (2100616, 2, 3); // 檢測到uk有沖突鍵,需要獲取記錄(2, 2100619) 的X鎖,等待session 2。 Session 2: * a)標記刪除記錄(2, 2100619),同時插入新記錄(2, 2100614); * b) (2, 2100614) 比(2, 2100619) 要小,因此定位到該記錄之前,也就是系統記錄infimum; * c)infimum記錄的下一條記錄(2, 2100619)上有鎖等待,需要升級成插入意向X鎖,導致死鎖發生。 **如果Purge線程一直停止,會發生什么呢 ?** 我們隨便建一個表,然后執行FLUSH TABLE tbname FOR EXPORT來讓purge線程停止。 假設當前表上數據為: ~~~ root@sb1 10:26:05>select * from t1; +---------+------+------+ | a | b | c | +---------+------+------+ | 2100710 | 2 | 3 | +---------+------+------+ 1 row in set (0.00 sec) ~~~ Session 1:replace into t1 values (2100720, 2, 3); 此時Page上存在記錄(infimum), (2, 2100710), (2, 2100720), (supremum)。 Session 2:replace into t1 values (2100715, 2, 3); 同上例,使用gdb斷到函數row_update_for_mysql。由于沒有啟動purge線程,因此老的被標記刪除的記錄還存在于page內,在掃描二級索引重復鍵時,也會依次給這些老記錄加鎖,因此session 2會持有 (2, 2100710)和 (2, 2100720)的X鎖。 Session 3:replace into t1 values (2100718, 2, 3); // 被session2阻塞,等待(2,2100710)的X鎖 Session 2:在標記刪除二級索引記錄,并進行插入時,選擇的插入位置為 (2, 2100710), (2,2100720)之間,插入點的下一條記錄(2,2100720)上沒有其他線程鎖等待,當前session鎖升級成功; 完成插入后,page上的記錄分布為(infimum), (2, 2100710), (2, 2100715), (2, 2100720), (supremum)。 Session 3:完成插入,最終page內的記錄為(infimum), (2, 2100710), (2, 2100715), (2, 2100718), (2, 2100720), (supremum)。其中只有用戶記錄(2, 2100718)未被標記刪除。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看