amamanamam

データベースと仲良くなりたいです

insert intention lockについての話

環境

mysql> select version();
+--------------+
| version()    |
+--------------+
| 8.0.28-debug |
+--------------+
1 row in set (0.00 sec)

mysql> show create table child\G
*************************** 1. row ***************************
       Table: child
Create Table: CREATE TABLE `child` (
  `id` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
1 row in set (0.00 sec)

インテンションロックとは

まずはそもそもインテンションロックってなんだっけの話をする。 MySQL公式の説明は以下となる

InnoDB では、行ロックとテーブルロックの共存を許可する複数粒度ロックがサポートされています。 たとえば、LOCK TABLES ... WRITE などのステートメントは、指定されたテーブルに対して排他ロック (X ロック) を取得します。 複数の粒度レベルでロックするには、InnoDB で intention locks を使用します。 インテントロックは、トランザクションが後でテーブルの行に必要とするロックのタイプ (共有または排他) を示すテーブルレベルのロックです。 インテントロックには、次の 2 種類があります: ・ intention shared lock (IS) は、トランザクションがテーブルの個々の行に shared ロックを設定することを示します。 ・intention exclusive lock (IX) は、トランザクションがテーブル内の個々の行に排他ロックを設定することを示します。 たとえば、SELECT ... FOR SHARE は IS ロックを設定し、SELECT ... FOR UPDATE は IX ロックを設定します。

記載の通り、SELECT ... FOR SHARE/UPDATE等々で行ロックを取得する前にインテンションロック(IX/IS)というテーブルロックが獲得される。

このロックは後続の行ロックを意図するためのロックである。例えばSELECT ... FOR UPDATEで行の排他ロックを取る前に、このテーブルレベルのインテンションロックが付与されて「これからこのテーブルの行の排他ロックが行われるぞ」という事を指し示してくれるのである。 なお、このロックはインテンションロック同士は互換性がある。テーブルレベルのロックタイプの互換性は以下のようになっている

X IX S IS
X 競合 競合 競合 競合
IX 競合 互換 競合 互換
S 競合 競合 互換 互換
IS 競合 互換 互換 互換

つまり、行レベルロックと通常のテーブルロック(X/S)の競合をテーブルレベルのロック同士の競合の話に置き換えることができるということである。インテンションロックが無ければ行レベルロックとテーブルレベルロックの異なるレベルでの競合比較が必要となる。異なるレベル同士の比較を許可するとややこしそう。

ギャップロック

次にギャップロックてそもそもなんだっけの話をする。 MySQL公式の説明は以下となる

ギャップロックは、インデックスレコード間のギャップのロック、または最初のインデックスレコードの前または最後のインデックスレコードの後のギャップのロックです。 たとえば、SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;では、範囲内の既存のすべての値間のギャップがロックされているため、カラムにそのような値がすでに存在するかどうかにかかわらず、他のトランザクションが 15 の値をカラム t.c1 に挿入できなくなります。

他のブログでもよく取り上げれられている話題なのでサラッと説明する。

公式の説明の通り、ギャップロックとはインデックスレコードの論理的なギャップに関するロックである。これによりファントムリード、つまり「あるトランザクションのINSERTにより別トランザクションで再度読み取りした結果が変わってしまうこと」を防ぐことができる。

ちなみに以下のように通常のギャップロック同士は競合しない

mysql1> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
Query OK, 0 rows affected, 1 warning (0.06 sec)

mysql1> INSERT INTO child (id) values (90),(102);
Query OK, 2 rows affected (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql1> begin;
Query OK, 0 rows affected (0.00 sec)

mysql1>select * from child where id between 99 and 100 for update;
Empty set (0.00 sec)

--別トランザクション
mysql2>begin;
Query OK, 0 rows affected (0.01 sec)

mysql2>select * from child where id between 99 and 100 for update;
Empty set (0.00 sec)

--gapが同じ範囲でgrantされている
mysql1>SELECT * FROM performance_schema.data_locks\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569830888:1093:140023450130672
ENGINE_TRANSACTION_ID: 26680
            THREAD_ID: 56
             EVENT_ID: 22
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140023450130672
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569830888:32:4:3:140023450127696
ENGINE_TRANSACTION_ID: 26680
            THREAD_ID: 56
             EVENT_ID: 22
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450127696
            LOCK_TYPE: RECORD
            LOCK_MODE: X,GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 102
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:1093:140023450124352
ENGINE_TRANSACTION_ID: 26679
            THREAD_ID: 55
             EVENT_ID: 38
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140023450124352
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:32:4:3:140023450121296
ENGINE_TRANSACTION_ID: 26679
            THREAD_ID: 55
             EVENT_ID: 38
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450121296
            LOCK_TYPE: RECORD
            LOCK_MODE: X,GAP
          LOCK_STATUS: GRANTED
            LOCK_DATA: 102
4 rows in set (0.00 sec)

挿入インテンションロック

では挿入インテンションロックについて触れていく MySQL公式の説明は以下である

挿入意図ロックは、行の挿入前に INSERT 操作によって設定されるギャップロックのタイプです。 このロックは、同じインデックスギャップに挿入する複数のトランザクションは、そのギャップ内の同じ場所に挿入しなければ相互に待機する必要がないように、意図的に挿入することを示しています。 値が 4 と 7 のインデックスレコードが存在すると仮定します。 5 と 6 の値をそれぞれ挿入しようとする個別のトランザクションでは、挿入された行の排他ロックを取得する前に、挿入意図ロックを使用して 4 と 7 のギャップがロックされますが、行が競合していないため相互にブロックされません。

INSERTで行の挿入前に挿入インテンションロックというギャップロックが獲得される。 前述のインテンションロックはテーブルレベルのロックであることに注意。

このロックは後続の挿入を意図するロックである。INSERTで行の挿入を行う前にこのギャップロックが取得されて「これからこのギャップに行を挿入するぞ」ということを指し示してくれる。 なお、挿入インテンションロック同士は互換性があり、挿入インテンションロックと通常のギャップロックは競合する(後の例で確認する)

つまり、ギャップロックでINSERTを防ぐ話をギャップロック同士の競合の話に置き換えることができるということである。

挿入インテンションロックの例でも見てみる

公式説明にある例示を実際にやってみることにする。

クライアント A は、2 つのインデックスレコード (90 および 102) を含むテーブルを作成し、100 を超える ID を持つインデックスレコードに排他ロックを設定するトランザクションを開始します。 排他ロックには、レコード 102 の前にギャップロックが含まれます:

mysql1> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
Query OK, 0 rows affected, 1 warning (0.06 sec)

mysql1> INSERT INTO child (id) values (90),(102);
Query OK, 2 rows affected (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql1> begin;
Query OK, 0 rows affected (0.00 sec)

mysql1> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+
1 row in set (0.00 sec)

mysql1>SELECT * FROM performance_schema.data_locks\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:1093:140023450124352
ENGINE_TRANSACTION_ID: 26665
            THREAD_ID: 48
             EVENT_ID: 30
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140023450124352
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:32:4:1:140023450121296
ENGINE_TRANSACTION_ID: 26665
            THREAD_ID: 48
             EVENT_ID: 30
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450121296
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: supremum pseudo-record
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:32:4:3:140023450121296
ENGINE_TRANSACTION_ID: 26665
            THREAD_ID: 48
             EVENT_ID: 30
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450121296
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 102
3 rows in set (0.02 sec)

↑data_locksの結果からも分かるように、開区間(90, +∞)の範囲でロックを取得している。

クライアント B はトランザクションを開始して、ギャップにレコードを挿入します。 トランザクションは、排他ロックの取得を待機している間、挿入意図ロックを取得します。

mysql2>begin;
Query OK, 0 rows affected (0.00 sec)

mysql2>INSERT INTO child (id) VALUES (101);
--待ちが発生

mysql1>SELECT * FROM  sys.innodb_lock_waits\G
*************************** 1. row ***************************
                wait_started: 2023-10-18 09:20:47
                    wait_age: 00:00:03
               wait_age_secs: 3
                locked_table: `kubo`.`child`
         locked_table_schema: kubo
           locked_table_name: child
      locked_table_partition: NULL
   locked_table_subpartition: NULL
                locked_index: PRIMARY
                 locked_type: RECORD
              waiting_trx_id: 26666
         waiting_trx_started: 2023-10-18 09:20:47
             waiting_trx_age: 00:00:03
     waiting_trx_rows_locked: 1
   waiting_trx_rows_modified: 0
                 waiting_pid: 9
               waiting_query: INSERT INTO child (id) VALUES (101)
             waiting_lock_id: 140023569830888:32:4:3:140023450127696
           waiting_lock_mode: X,GAP,INSERT_INTENTION
             blocking_trx_id: 26665
                blocking_pid: 8
              blocking_query: SELECT * FROM  sys.innodb_lock_waits
            blocking_lock_id: 140023569829880:32:4:3:140023450121296
          blocking_lock_mode: X
        blocking_trx_started: 2023-10-18 09:19:41
            blocking_trx_age: 00:01:09
    blocking_trx_rows_locked: 2
  blocking_trx_rows_modified: 0
     sql_kill_blocking_query: KILL QUERY 8
sql_kill_blocking_connection: KILL 8
1 row in set (0.08 sec)

mysql1>SELECT * FROM performance_schema.data_locks\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569830888:1093:140023450130672
ENGINE_TRANSACTION_ID: 26666
            THREAD_ID: 49
             EVENT_ID: 19
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140023450130672
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569830888:32:4:3:140023450127696
ENGINE_TRANSACTION_ID: 26666
            THREAD_ID: 49
             EVENT_ID: 19
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450127696
            LOCK_TYPE: RECORD
            LOCK_MODE: X,GAP,INSERT_INTENTION
          LOCK_STATUS: WAITING
            LOCK_DATA: 102
*************************** 3. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:1093:140023450124352
ENGINE_TRANSACTION_ID: 26665
            THREAD_ID: 48
             EVENT_ID: 30
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140023450124352
            LOCK_TYPE: TABLE
            LOCK_MODE: IX
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 4. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:32:4:1:140023450121296
ENGINE_TRANSACTION_ID: 26665
            THREAD_ID: 48
             EVENT_ID: 30
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450121296
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: supremum pseudo-record
*************************** 5. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140023569829880:32:4:3:140023450121296
ENGINE_TRANSACTION_ID: 26665
            THREAD_ID: 48
             EVENT_ID: 30
        OBJECT_SCHEMA: kubo
          OBJECT_NAME: child
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY
OBJECT_INSTANCE_BEGIN: 140023450121296
            LOCK_TYPE: RECORD
            LOCK_MODE: X
          LOCK_STATUS: GRANTED
            LOCK_DATA: 102
5 rows in set (0.01 sec)

innodb_lock_waitsとdata_locksからも分かるように、2番目のトランザクションのINSERTの際に挿入意図ロックの待機をしている事が確認できる。

挿入インテンションロックのデッドロックの例でも見てみる

select .. for updateで拾ってレコードが無ければinsertするみたいなロジックが同時に走れば起こり得る。

mysql1>select * from child;
+-----+
| id  |
+-----+
|  90 |
| 102 |
+-----+
2 rows in set (0.01 sec)

mysql1>begin;
Query OK, 0 rows affected (0.00 sec)

mysql1>select * from child where id =98 for update;
Empty set (0.00 sec)

mysql2>begin;
Query OK, 0 rows affected (0.00 sec)

mysql2>select * from child where id =99 for update;
Empty set (0.00 sec)

mysql1>insert into child(id) value(98);
--待機

mysql2>insert into child(id) value(99);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

mysql> show engine innodb status

...
------------------------
LATEST DETECTED DEADLOCK
------------------------
2023-10-25 09:24:10 140022342727424
*** (1) TRANSACTION:
TRANSACTION 26687, ACTIVE 45 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1192, 2 row lock(s)
MySQL thread id 11, OS thread handle 140023115962112, query id 204 localhost root update
insert into child(id) value(98)

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 32 page no 4 n bits 72 index PRIMARY of table `kubo`.`child` trx id 26687 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000006822; asc     h";;
 2: len 7; hex 810000010e011d; asc        ;;


*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 32 page no 4 n bits 72 index PRIMARY of table `kubo`.`child` trx id 26687 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000006822; asc     h";;
 2: len 7; hex 810000010e011d; asc        ;;


*** (2) TRANSACTION:
TRANSACTION 26688, ACTIVE 32 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1192, 2 row lock(s)
MySQL thread id 12, OS thread handle 140023518537472, query id 205 localhost root update
insert into child(id) value(99)

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 32 page no 4 n bits 72 index PRIMARY of table `kubo`.`child` trx id 26688 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000006822; asc     h";;
 2: len 7; hex 810000010e011d; asc        ;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 32 page no 4 n bits 72 index PRIMARY of table `kubo`.`child` trx id 26688 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000006822; asc     h";;
 2: len 7; hex 810000010e011d; asc        ;;

*** WE ROLL BACK TRANSACTION (2)

これはid=98,id=99のselect..for updateでギャップロックを取得し、その後互いのinsertによる挿入インテンションロックと競合してデッドロックが発生しているということである。

おまけ

※以下の話はまだ根拠が不十分なのでテキトーに見て欲しい。

あるトランザクションのinsertが他トランザクションによってブロックされている際に、data_locksテーブルを参照することで挿入インテンションロックの存在を確認できた。 一方で、1トランザクションでinsertをする際(特に競合していない時)にはdata_locksテーブルを参照しても挿入インテンションロックを確認できない。

これは、insertの前に一瞬だけ挿入インテンションロックが獲得されるためだと想像できる。すなわち、data_locksテーブルを参照するタイミングでは既にリリースされていると思われる。

では、ある適当なタイミングでブレークポイントを置いて、insert実施時にそのタイミングでブレークさせた時、data_locksにINSERT_INTENTIONの行が現れるのだろうか。言い換えれば、INSERT_INTENTIONの文字面が拝める良いブレークポイント はあるのだろうか。

結論から言うと、INSERT_INTENTIONの文字面は拝めなかった。 「insertの前に一瞬だけ挿入インテンションロックが獲得される」という前提がそもそも怪しいかもしれない。つまり必ずしも挿入インテンションロックが獲得される訳では無いかもしれない。

insertの過程でlock_rec_insert_check_and_lockという挿入を妨げるロックをチェックするメソッドがある。 もしもそのようなロックが存在すれば、以下のようにtype_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTIONのロックを待機させる。つまり前章で見た例はこの場合に該当する(実際例で見た通りdata_locksにはLOCK_MODE=X,GAP,INSERT_INTENTIONの行が存在していた)

一方で、そのようなロックが存在しない場合は特に何もしない。そしてそれ以降特にINSERT_INTENTION獲得らしきフェーズは現れない(無いことの証明は難しいが恐らく)。またこの時点でのdata_locksにはLOCK_MODE=IXの行しか存在しない。

そのため、あくまで「先に競合するロックが存在しなければ挿入インテンションロックを取らない」と思われる。確かにそのようなロックが存在しなければ挿入の意図を表す必要もないかもしれない。

引き続き調べてみる。

{
    locksys::Shard_latch_guard guard{UT_LOCATION_HERE, block->get_page_id()};

    /* When inserting a record into an index, the table must be at
    least IX-locked. When we are building an index, we would pass
    BTR_NO_LOCKING_FLAG and skip the locking altogether. */
    ut_ad(lock_table_has(trx, index->table, LOCK_IX));

    /* Spatial index does not use GAP lock protection. It uses
    "predicate lock" to protect the "range" */
    ut_ad(!dict_index_is_spatial(index));

    lock = lock_rec_get_first(lock_sys->rec_hash, block, heap_no);

    if (lock == nullptr) {
      *inherit = false;
    } else {
      *inherit = true;

      /* If another transaction has an explicit lock request which locks
      the gap, waiting or granted, on the successor, the insert has to wait.

      An exception is the case where the lock by the another transaction
      is a gap type lock which it placed to wait for its turn to insert. We
      do not consider that kind of a lock conflicting with our insert. This
      eliminates an unnecessary deadlock which resulted when 2 transactions
      had to wait for their insert. Both had waiting gap type lock requests
      on the successor, which produced an unnecessary deadlock. */

      const ulint type_mode = LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION;

      const lock_t *wait_for =
          lock_rec_other_has_conflicting(type_mode, block, heap_no, trx);

      if (wait_for != nullptr) {
        RecLock rec_lock(thr, index, block, heap_no, type_mode);

        trx_mutex_enter(trx);

        err = rec_lock.add_to_waitq(wait_for);

        trx_mutex_exit(trx);
      }
    }
  } /* Shard_latch_guard */

※lock_rec_insert_check_and_lockまでのバックトレース

lock_rec_insert_check_and_lock(ulint flags, const rec_t * rec, buf_block_t * block, dict_index_t * index, que_thr_t * thr, mtr_t * mtr, ulint * inherit) (/mysql-8.0.28/storage/innobase/lock/lock0lock.cc:5206)
btr_cur_ins_lock_and_undo(ulint flags, btr_cur_t * cursor, dtuple_t * entry, que_thr_t * thr, mtr_t * mtr, ulint * inherit) (/mysql-8.0.28/storage/innobase/btr/btr0cur.cc:2591)
btr_cur_optimistic_insert(ulint flags, btr_cur_t * cursor, ulint ** offsets, mem_heap_t ** heap, dtuple_t * entry, rec_t ** rec, big_rec_t ** big_rec, que_thr_t * thr, mtr_t * mtr) (/mysql-8.0.28/storage/innobase/btr/btr0cur.cc:2810)
row_ins_clust_index_entry_low(uint32_t flags, ulint mode, dict_index_t * index, ulint n_uniq, dtuple_t * entry, que_thr_t * thr, bool dup_chk_only) (/mysql-8.0.28/storage/innobase/row/row0ins.cc:2554)
row_ins_clust_index_entry(dict_index_t * index, dtuple_t * entry, que_thr_t * thr, bool dup_chk_only) (/mysql-8.0.28/storage/innobase/row/row0ins.cc:3139)
row_ins_index_entry(dict_index_t * index, dtuple_t * entry, uint32_t & multi_val_pos, que_thr_t * thr) (/mysql-8.0.28/storage/innobase/row/row0ins.cc:3331)
row_ins_index_entry_step(ins_node_t * node, que_thr_t * thr) (/mysql-8.0.28/storage/innobase/row/row0ins.cc:3467)
row_ins(ins_node_t * node, que_thr_t * thr) (/mysql-8.0.28/storage/innobase/row/row0ins.cc:3586)
row_ins_step(que_thr_t * thr) (/mysql-8.0.28/storage/innobase/row/row0ins.cc:3710)
row_insert_for_mysql_using_ins_graph(const byte * mysql_rec, row_prebuilt_t * prebuilt) (/mysql-8.0.28/storage/innobase/row/row0mysql.cc:1583)
row_insert_for_mysql(const byte * mysql_rec, row_prebuilt_t * prebuilt) (/mysql-8.0.28/storage/innobase/row/row0mysql.cc:1713)
ha_innobase::write_row(ha_innobase * const this, uchar * record) (/mysql-8.0.28/storage/innobase/handler/ha_innodb.cc:8920)
handler::ha_write_row(handler * const this, uchar * buf) (/mysql-8.0.28/sql/handler.cc:7937)
write_record(THD * thd, TABLE * table, COPY_INFO * info, COPY_INFO * update) (/mysql-8.0.28/sql/sql_insert.cc:2165)
Sql_cmd_insert_values::execute_inner(Sql_cmd_insert_values * const this, THD * thd) (/mysql-8.0.28/sql/sql_insert.cc:635)
Sql_cmd_dml::execute(Sql_cmd_dml * const this, THD * thd) (/mysql-8.0.28/sql/sql_select.cc:581)
mysql_execute_command(THD * thd, bool first_level) (/mysql-8.0.28/sql/sql_parse.cc:3553)
dispatch_sql_command(THD * thd, Parser_state * parser_state) (/mysql-8.0.28/sql/sql_parse.cc:5174)
dispatch_command(THD * thd, const COM_DATA * com_data, enum_server_command command) (/mysql-8.0.28/sql/sql_parse.cc:1938)
do_command(THD * thd) (/mysql-8.0.28/sql/sql_parse.cc:1352)