2013年4月8日月曜日

REPLACE INTOが実はINSERT + DELETEだったと思ったら、結局DELETE + INSERTだった

REPLACE INTOが実はINSERT + DELETEだった(INSERTが先で後から消す)の続き。

REPLACE INTOは「DELETE + INSERT(その行を消してからもう一度書く)」だと今まで思っていたけれど、
実は「INSERT + DELETE(行を突っ込んでから古い行を消す)」な動きっぽいというはなし。

そっちが先なんだびっくりー、という感じで書いたのですが、
色々手落ちがあったので後から調べたことをメモ。


1) その結果だと時間が一緒だから、「DELETEトリガーがINSERTトリガーより先」というのは断言できない。順番は必ずしも保証されないから。
⇒TRIGGERの中身をSYSDATE(6)に書き換えてみました。

mysql56> delimiter //

 mysql56> CREATE TRIGGER tr1
    -> BEFORE INSERT ON t2
    -> FOR EACH ROW BEGIN
    ->  INSERT INTO t3(dateval, trg, val) VALUES (SYSDATE(6), 'before insert', new.num);
    -> END//
Query OK, 0 rows affected (0.01 sec)

 mysql56> CREATE TRIGGER tr2
    -> BEFORE DELETE ON t2
    -> FOR EACH ROW BEGIN
    ->  INSERT INTO t3(dateval, trg, val) VALUES (SYSDATE(6), 'before delete', old.num);
    -> END//
Query OK, 0 rows affected (0.01 sec)

 mysql56> delimiter ;

 
mysql56> REPLACE INTO t2 VALUES (1);
Query OK, 1 row affected (0.01 sec)

 mysql56> REPLACE INTO t2 VALUES (1);
Query OK, 2 rows affected (0.03 sec)

 mysql56> SELECT * FROM t3;
+----------------------------+---------------+------+
| dateval                    | trg           | val  |
+----------------------------+---------------+------+
| 2013-04-08 10:41:00.414920 | before insert |    1 |
| 2013-04-08 10:41:01.249376 | before insert |    1 |
| 2013-04-08 10:41:01.249701 | before delete |    1 |
+----------------------------+---------------+------+
3 rows in set (0.00 sec)


綺麗に取れた。
DELETEトリガーの方がINSERTトリガーより後に来てる。

木村さんのブログを参考にさせていただきましたm(_"_)m
http://blog.kimuradb.com/?eid=877275

最初はSYSDATE(6)じゃなくてNOW(6)にしていたら、
NOW()の結果はキャッシュされるからマイクロ秒まで同じ値になっちゃって失敗してたりします。。orz



2) INSERTトリガーがDELETEトリガーより先に引かれていることとINSERTがDELETEより先に発生していることは同値ではない。
⇒データファイルを直接叩けるようにMyISAMエンジンにしてステップ実行。。

mysql56> select * from t2;
+-----+------+
| num | val  |
+-----+------+
|   1 | one  |
+-----+------+
1 row in set (0.00 sec)

# od -c t2.MYD
0000000 003  \0 016 002  \0 376 001  \0  \0  \0  \0  \0  \0  \0 003   o
0000020   n   e  \0  \0
0000024

# gdb -p `pidof mysqld`

(gdb) b /home/yoku0825/mysql-5.6.10/sql/sql_insert.cc:1787
Breakpoint 1 at 0x6af7bc: file /home/yoku0825/mysql-5.6.10/sql/sql_insert.cc, line 1787.

取り敢えず前回目星をつけたwrite_record関数のDELETEトリガーを引く前のあたりにブレークポイントを入れる。

mysql56> replace into t2 values (1, 'two');

(gdb) c
Continuing.
[Switching to Thread 0x7f6c207d9700 (LWP 21484)]

Breakpoint 1, write_record (thd=0x43ab7c0, table=0x4414280, info=0x7f6c207d76f0, update=0x7f6c207d7670)
    at /home/yoku0825/mysql-5.6.10/sql/sql_insert.cc:1787
1787            if (last_uniq_key(table,key_nr) &&
(gdb) n
1808              if (table->triggers &&
(gdb)
1812              if ((error=table->file->ha_delete_row(table->record[1])))

od -cを叩きながらポチポチやってたら、ここで

# od -c t2.MYD
0000000  \0  \0  \0 024 377 377 377 377 377 377 377 377 377 377 377 377
0000020 377 377 377 377
0000024

Σ(゚д゚lll) 先に消えた!


(gdb)
1618        while ((error=table->file->ha_write_row(table->record[0])))

# od -c t2.MYD
0000000 003  \0 016 002  \0 376 001  \0  \0  \0  \0  \0  \0  \0 003   t
0000020   w   o  \0  \0
0000024

Σ(゚д゚lll) ここで書いた!


orz

whileでエラー(Err:121 Duplicate key on write or update)があったらすっ飛ばして先にha_delete_rowに行ってからha_write_rowに来るんですね。。

あー、もう、なんだかなーorz

REPLACE INTOは行を消してから新しい行を書く。
けど、トリガーはINSERTトリガーが先に引かれてDELETEトリガーが後から引かれる。

でした。
ちなみに、sql/sql_insert.ccの1787では既にINSERTトリガーは引かれてました。
確かに先頭近くで一度ha_write_rowしてるからそこで引いてるんだと思うですよ。


@meijik さん、@sakaik さん、ツッコミありがとうございましたm(_"_)m


【2013/04/08 12:38】
AFTERトリガーは入れ子になるですよ。

mysql56> select * from t3;
+----------------------------+---------------+------+
| dateval                    | trg           | val  |
+----------------------------+---------------+------+
| 2013-04-08 12:37:17.081148 | before insert |    1 |
| 2013-04-08 12:37:17.081350 | before delete |    1 |
| 2013-04-08 12:37:17.081412 | after delete  |    1 |
| 2013-04-08 12:37:17.081452 | after insert  |    1 |
+----------------------------+---------------+------+
4 rows in set (0.00 sec)

0 件のコメント :

コメントを投稿