Sample Header Ad - 728x90

Understanding INSERT ... ON DUPLICATE KEY UPDATE deadlock scenario

3 votes
1 answer
4949 views
I'm trying to understand a scenario where an INSERT ... ON DUPLICATE KEY UPDATE statement is causing deadlocks under high concurrency. The two tables in question: hosts: "CREATE TABLE hosts (" + "id int(10) unsigned NOT NULL AUTO_INCREMENT," + "osquery_host_id varchar(255) NOT NULL," + "created_at timestamp DEFAULT CURRENT_TIMESTAMP," + "updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP," + "deleted_at timestamp NULL DEFAULT NULL," + "deleted tinyint(1) NOT NULL DEFAULT FALSE," + "detail_update_time timestamp NULL DEFAULT NULL," + "node_key varchar(255) DEFAULT NULL," + "host_name varchar(255) NOT NULL DEFAULT ''," + "uuid varchar(255) NOT NULL DEFAULT ''," + "platform varchar(255) NOT NULL DEFAULT ''," + "osquery_version varchar(255) NOT NULL DEFAULT ''," + "os_version varchar(255) NOT NULL DEFAULT ''," + "build varchar(255) NOT NULL DEFAULT ''," + "platform_like varchar(255) NOT NULL DEFAULT ''," + "code_name varchar(255) NOT NULL DEFAULT ''," + "uptime bigint(20) NOT NULL DEFAULT 0," + "physical_memory bigint(20) NOT NULL DEFAULT 0," + "cpu_type varchar(255) NOT NULL DEFAULT ''," + "cpu_subtype varchar(255) NOT NULL DEFAULT ''," + "cpu_brand varchar(255) NOT NULL DEFAULT ''," + "cpu_physical_cores int NOT NULL DEFAULT 0," + "cpu_logical_cores int NOT NULL DEFAULT 0," + "hardware_vendor varchar(255) NOT NULL DEFAULT ''," + "hardware_model varchar(255) NOT NULL DEFAULT ''," + "hardware_version varchar(255) NOT NULL DEFAULT ''," + "hardware_serial varchar(255) NOT NULL DEFAULT ''," + "computer_name varchar(255) NOT NULL DEFAULT ''," + "primary_ip_id INT(10) UNSIGNED DEFAULT NULL, " + "PRIMARY KEY (id)," + "UNIQUE KEY idx_host_unique_nodekey (node_key)," + "UNIQUE KEY idx_osquery_host_id (osquery_host_id)," + "FULLTEXT KEY hosts_search (host_name)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;", networks_interfaces: "CREATE TABLE network_interfaces (" + "id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT," + "host_id INT(10) UNSIGNED NOT NULL," + "mac varchar(255) NOT NULL DEFAULT ''," + "ip_address varchar(255) NOT NULL DEFAULT ''," + "broadcast varchar(255) NOT NULL DEFAULT ''," + "ibytes BIGINT NOT NULL DEFAULT 0," + "interface VARCHAR(255) NOT NULL DEFAULT ''," + "ipackets BIGINT NOT NULL DEFAULT 0," + "last_change BIGINT NOT NULL DEFAULT 0," + "mask varchar(255) NOT NULL DEFAULT ''," + "metric INT NOT NULL DEFAULT 0," + "mtu INT NOT NULL DEFAULT 0," + "obytes BIGINT NOT NULL DEFAULT 0," + "ierrors BIGINT NOT NULL DEFAULT 0," + "oerrors BIGINT NOT NULL DEFAULT 0," + "opackets BIGINT NOT NULL DEFAULT 0," + "point_to_point varchar(255) NOT NULL DEFAULT ''," + "type INT NOT NULL DEFAULT 0," + "PRIMARY KEY (id), " + "FOREIGN KEY idx_network_interfaces_hosts_fk (host_id) " + "REFERENCES hosts(id) " + "ON DELETE CASCADE, " + "FULLTEXT KEY ip_address_search (ip_address)," + "UNIQUE KEY idx_network_interfaces_unique_ip_host_intf (ip_address, host_id, interface)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;", Lastest deadlock info: ------------------------ LATEST DETECTED DEADLOCK ------------------------ 2020-01-20 00:09:06 0x2b033abd2700 *** (1) TRANSACTION: TRANSACTION 78516922, ACTIVE 0 sec inserting mysql tables in use 1, locked 1 LOCK WAIT 5 lock struct(s), heap size 1136, 3 row lock(s), undo log entries 2 MySQL thread id 286926, OS thread handle 47297573750528, query id 1045761878 10.107.51.236 username update INSERT INTO network_interfaces ( host_id, mac, ip_address, broadcast, ibytes, interface, ipackets, last_change, mask, metric, mtu, obytes, ierrors, oerrors, opackets, point_to_point, type ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), mac = VALUES(mac), broadcast = VALUES(broadcast), ibytes = VALUES(ibytes), ipackets = VALUES(ipackets), last_change = VALUES(last_change), mask = VALUES(mask), metric = VALUES(metric), mtu = VALUES(mtu), obytes = VALUES(obytes), ierrors = VALUES(ierrors), oerrors = VALUES(oerrors), opackets = VALUES(opackets), point_to_point = VALUES(point_to_point), type = VALUES(type) *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 258 page no 2729 n bits 408 index FTS_DOC_ID_INDEX of table kolide.network_interfaces trx id 78516922 lock_mode Xinsert intention waiting Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** (2) TRANSACTION: TRANSACTION 78516915, ACTIVE 0 sec inserting mysql tables in use 1, locked 1 18 lock struct(s), heap size 1136, 33 row lock(s), undo log entries 12 MySQL thread id 281276, OS thread handle 47292870371072, query id 1045761879 10.107.78.241 username update INSERT INTO network_interfaces ( host_id, mac, ip_address, broadcast, ibytes, interface, ipackets, last_change, mask, metric, mtu, obytes, ierrors, oerrors, opackets, point_to_point, type ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), mac = VALUES(mac), broadcast = VALUES(broadcast), ibytes = VALUES(ibytes), ipackets = VALUES(ipackets), last_change = VALUES(last_change), mask = VALUES(mask), metric = VALUES(metric), mtu = VALUES(mtu), obytes = VALUES(obytes), ierrors = VALUES(ierrors), oerrors = VALUES(oerrors), opackets = VALUES(opackets), point_to_point = VALUES(point_to_point), type = VALUES(type) *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 258 page no 2729 n bits 408 index FTS_DOC_ID_INDEX of table kolide.network_interfaces trx id 78516915 lock_mode X Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** (2) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 258 page no 2729 n bits 408 index FTS_DOC_ID_INDEX of table kolide.network_interfaces trx id 78516915 lock_mode Xinsert intention waiting Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 0: len 8; hex 73757072656d756d; asc supremum;; *** WE ROLL BACK TRANSACTION (1) The program starts a transaction, updates a host row, and uses that same transaction in a loop through all the host's interfaces and issues a INSERT...ON DUPLICATE statement for each interface. As I understand it, because the transaction starts with a UPDATE (exclusive) lock on the host table, another transaction cannot be updating the same host. So I don't think this is a scenario where two connections are trying to update the same set of host interfaces (this could easily result in a deadlock). I think this might be due to different host updates competing over the AUTO_INCREMENT index of network_interfaces? I just don't understand how, even after staring at the MySQL docs about locks. I understand that transaction 1 is waiting on an exclusive insert lock, transaction 2 has an exclusive lock and is also waiting on an exclusive insert lock. What I'm specifically not understanding is why TRANSACTION 2 has the exclusive lock lock_mode X to begin with.
Asked by billcobbler (81 rep)
Jan 20, 2020, 08:32 PM
Last activity: Apr 15, 2025, 04:00 PM