Mysql Deadlock in SELECT.. if nothing exists INSERT - mysql

I have a simple split testing table:
CREATE TABLE `tracked_split_test_track_variant` (
 `tracked_split_test_id` int(10) unsigned NOT NULL,
 `track_id` bigint(20) unsigned NOT NULL,
 `variant` char(1) NOT NULL,  
PRIMARY KEY (`tracked_split_test_id`,`track_id`),
 KEY `tracked_split_test_track_variant_1` (`tracked_split_test_id`),
 KEY `tracked_split_test_track_variant_2` (`track_id`),
 CONSTRAINT `fk_tracked_split_test_track_variant_2`
FOREIGN KEY (`track_id`)
REFERENCES `track` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE,
 CONSTRAINT `fk_tracked_split_test_track_variant_1`
FOREIGN KEY (`tracked_split_test_id`)
REFERENCES `tracked_split_test` (`id`)
ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
Where variant is a randomly A or B.
When the system asks 'Which variant should I show this user?' I want the following things to happen:
SELECT the tracked_split_test_track_variant that belongs to the current track and tracked_split_test
If no record exists create a new one with a random variant and INSERT it
Currently I'm running the SELECT and (optional) INSERT query in a transaction as:
SELECT *
FROM tracked_split_test_track_variant
WHERE track_id = :track_id
AND tracked_split_test_id = :tracked_split_test_id
FOR UPDATE
and
INSERT
INTO tracked_split_test_track_variant
VALUES (:track_id, :tracked_split_test_id, :variant)
I added the FOR UPDATE to the SELECT so that if two transactions were running with the same details.. I wouldn't get two INSERT attempts.
Even though I commit as soon as possible, I'm now getting deadlocks instead. Have I done this all wrong?

As the tuples are immutable outside of this transaction, I have come up with the following solution using explicit named locks:
SELECT GET_LOCK('tracked_split_test_track_variant-xxx-yyy',30);
SELECT *
FROM tracked_split_test_track_variant
WHERE tracked_split_test_id = :tracked_split_test_id
AND track_id = :track_id
[
INSERT
INTO tracked_split_test_track_variant
VALUES (:track_id, :tracked_split_test_id, :variant)
]
DO RELEASE_LOCK('tracked_split_test_track_variant-xxx-yyy');
Where xxx is the tracked_split_test_id and yyy is the track_id
I would be interested to see if there is a better solution out there however.

Related

MySQL. Why I cant update one only one column?

I have table:
CREATE TABLE `cold_water_volume_value` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`parameter_value_id` int(11) NOT NULL,
`time` timestamp(4) NOT NULL DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(4),
`value` double NOT NULL,
`device_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_cold_water_volume_value_id_device_time` (`parameter_value_id`,`device_id`,`time`),
KEY `idx_cold_water_volume_value_id_time` (`parameter_value_id`,`time`),
KEY `fk_cold_water_volume_value_device_id_idx` (`device_id`),
CONSTRAINT `fk_cold_water_volume_value_device_id` FOREIGN KEY (`device_id`) REFERENCES `device` (`id`) ON UPDATE SET NULL,
CONSTRAINT `fk_cold_water_volume_value_id` FOREIGN KEY (`parameter_value_id`) REFERENCES `cold_water_volume_parameter` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=684740 DEFAULT CHARSET=utf8;
And all rows have device_id = NULL. I want to update it by script:
UPDATE cold_water_volume_value SET device_id = 130101 WHERE parameter_value_id = 2120101;
But instead of replacing all device_id for picked parameter_value_id from null to given value, it sets all content of time and value columns to now () and some (seems like completely random from previous values) number.
Why it happens, and how to do it correct way?
time is automatically updated as per your schema.
`time` timestamp(4) NOT NULL DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(4)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To get around that you can set time to itself in your update.
UPDATE cold_water_volume_value
SET device_id = 130101, time = time
WHERE parameter_value_id = 2120101;
But that is likely there to track when the last time a row was updated. If so it's working as intended, leave it to do its thing.
As for value, that might have an update trigger on it. Check with show triggers and look for triggers on that table.
Your device_id is updated using content of time probably because in your index definition you mixed datatypes. It's worth noting that you should not mix datatypes especially on where clause when indexing.
Try to separate your indexes for example:
KEY idx_cold_water_volume_value_id_device_time (time),
KEY idx_cold_water_volume_value_id_device (parameter_value_id,device_id),
Try above statements for your definition and run query again.
It makes sense for the indexed column to have the same datatypes.
e.g. parameter_value_id and device_id

MySQL on duplicate key update is not updating

I'm trying to use on duplicate key update but it's not affecting any rows.
My table create statement, where you can see that I've created a unique key on childid and date.
CREATE TABLE `history_childfees` (
`childid` int(11) DEFAULT NULL,
`date` date DEFAULT NULL,
`amount` decimal(10,2) DEFAULT NULL,
`feetypecode` varchar(45) DEFAULT NULL,
UNIQUE KEY `key_childdate` (`childid`,`date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
These are the two rows I have in the table.
The row I'm trying to update is the first row, by changing the amount for child 86615 on 2019-03-22.
insert into history_childfees (childid,date,amount,feetypecode)
values(86615,'2019-03-22',50,'DAY')
on duplicate key update childid = 86615, date = '2019-03-22';
I've also tried this syntax.
insert into history_childfees (childid,date,amount,feetypecode)
values (86615,'2019-03-22',50,'DAY')
on duplicate key update childid = values(childid), date = values(date);
Either way, it does not perform an insert and there's no error when I execute but it affects 0 rows. What am I missing here?
Consider:
CREATE TABLE `history_childfees` (
...
UNIQUE KEY `key_childdate` (`childid`,`date`)
);
And:
insert into history_childfees
...
on duplicate key update childid = 86615, date = '2019-03-22'
The columns that you update on duplicate key are exactly those of the UNIQUE KEY that you are using to identify duplicates. By design, we already know that the values do match... As a consequence, the query leaves duplicate records unmodified.
If I followed you correctly, you probably want:
insert into history_childfees
...
on duplicate key update amount = 50

Is it possible to update only a single field with ON DUPLICATE KEY UPDATE in a table with multiple fields?

Is it possible to update only a single field with ON DUPLICATE KEY UPDATE in a table with multiple fields?
If I have a table with three fields; key, cats, dogs where key is the primary key is it possible to update a record on duplicate key, changing only one field, (for my example cats) without changing the value in the other non-key fields (dogs). This is without knowing what the value of dogs from outside of the database at the time of insertion (i.e. I have a variable holding cats value, but not one holding dogs value)
INSERT INTO `myTable` (`key`,`cats`) VALUES('someKey1','Manx') ON DUPLICATE KEY UPDATE `cats` = 'Manx';
At the moment when I run something like this and the key already exists in the table dogs is set to NULL even when it had a value previously.
Gordon is right, it does not work the way I described. If you see this, it is not caused by the ON DUPLICATE UPDATE statement, but something else. Here is the proof:
CREATE TABLE IF NOT EXISTS `myTable` (
`key` varchar(20) NOT NULL default '',
`cats` varchar(20) default NULL,
`dogs` varchar(20) default NULL,
PRIMARY KEY (`key`)
)
The run
INSERT INTO `myTable` (`key`, `cats`, `dogs`) VALUES
('someKey1', NULL, 'jack-russell');
Then run
INSERT INTO `myTable` (`key`,`cats`) VALUES
('someKey1','Manx') ON DUPLICATE KEY UPDATE `cats` = 'manx';
Then check the table
I think you should try to UPSERT.
Please examine this:
INSERT INTO `item` (`item_name`, items_in_stock) VALUES( 'A', 27)
ON DUPLICATE KEY UPDATE `new_items_count` = `new_items_count` + 27
MySQL UPSERT

Mysql Deleting all records when deleting a foreign key

I have two MySQL Tables
CREATE TABLE IF NOT EXISTS `orders` (
`order_id` int(5) NOT NULL AUTO_INCREMENT,
`order_address` varchar(255) NOT NULL,
`order_paymentmethod` varchar(50) NOT NULL,
`coupon_code` varchar(50) NOT NULL,
`user_id` int(5) NOT NULL,
PRIMARY KEY (`order_id`),
KEY `fk_orderuser` (`user_id`),
KEY `fk_ordercoupon` (`coupon_code`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
CREATE TABLE IF NOT EXISTS `coupons` (
`coupon_code` varchar(50) NOT NULL,
`coupon_discount` int(255) NOT NULL,
PRIMARY KEY (`coupon_code`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
When am deleting the coupon code from the coupon table the order record that the coupon is related too is also deleted. And i just want to delete the coupon code without any effect on the table orders
Is their any solution for that please?
Regards
In this case you can solve that problem by using mysql trigger. Create trigger for coupons table
CREATE TRIGGER `coupons_before_delete` AFTER DELETE ON `coupons` FOR EACH ROW BEGIN
-- INSERT INTO add_inout_to_error_log(msg) VALUES(old.coupon_code);
DELETE FROM orders WHERE orders.coupon_code = old.coupon_code;
END
in this code old.coupon_code is current deleted coupon code. You can get access to any field of deleted item
There are three options here
Don't delete the coupon, use another boolean field (eg deleted) with default = true, but set it to false when you want to remove it (it doesn't actually remove it but you can handle deleted coupons using this field). This way you can also track the orders initiated with a coupon that on the way this was deleted.
Remove the not null constraint from coupon_code varchar(50) NOT NULL (in orders table) and add a foreign key constraint to ON DELETE SET NULL. *For orders without a coupon set it to null from the beginning.
Using the known as NULL pattern. Create a dummy coupon (ZERO discount) in your db and instead of deleting coupons, assign this dummy coupon to orders that do not require a real coupon.
*depending on the "tracking" requirements, a combination of the above approaches may be required

MySQL stop inserting when duplicate key condition is met

I have a table constructed by the followinng:
CREATE TABLE IF NOT EXISTS test_table (
ID int(11) NOT NULL AUTO_INCREMENT,
ProfileID int(11) NOT NULL,
ForeignID int(11) NOT NULL,
PRIMARY KEY (ProfileID,ForeignID) )
ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
I want to do something a little peculiar though, say there are 4 records in the database:
RecA, RecB, RecC, RecD
I would like to run the following query and have the insert behavior stop when a duplicate key was encountered:
INSERT IGNORE INTO test_table (ProfileID, ForeignID) VALUES(RecE, RecF, RecA, RecB, RecG);
So the query would only insert RecE and RecF, is there a way to do this in MySQL, perhaps using ON DUPLICATE KEY? Ideally the execution would just be terminated once a duplicate has been found, I am not too familiar with SQL syntax though.
Where RecG was explicitly not inserted.