Endless deadlock situation on mySQL innoDB - mysql

Here is an issue which I encountered recently in my MySQLdb 5.7 (with innoDB engine) hosted in a Virtual Machine in my Google Cloud Platform account.
Suddenly, my DB came to a state where every transaction (such as logins) on a specific table named 'users' failed (endless timeout).
I took this error: pymysql.err.OperationalError: (1213, 'Deadlock found when trying to get lock; try restarting transaction'
Well, basically I try to find out if there is an automated recovery from this circled situation. I read in MySQL's site that by default is performed a rollback to last transaction, but why did't happen this in my situation? After a long time remaining in this situation I had to restart the MySQL server.
I need some directions on how to investigate it or take action for not facing it up again because it's a live database with customers.

MySQL's InnoDB Engine sports row-level locking, which can lead to deadlocks even when your code is inserting or updating a single row (specially if there are several indexes on the table being updated). Your best bet is to design the code around this in order to retry a transaction if it fails due to a deadlock. Some useful info about MySQL deadlock diagnose and possible workarounds is available in MySQL Official Documentation

Below is the stored procedure for the login process in which we detect the endless deadlock.
CREATE DEFINER=`root`#`%` PROCEDURE `login`(
in email1 varchar(45),
in password1 varchar(256),
in lastlogin datetime,
out s INT(1)
)
BEGIN
DECLARE p INT(11);
DECLARE counter TINYINT;
IF EXISTS (select * from user where user.email = email1 AND user.password = password1 AND user.locked = 0 AND user.inactive = 0) THEN
SELECT id INTO p FROM user WHERE user.email = email1;
SELECT user.id, user.id_user_roles, user.id_user_settings, user.first_name, user.last_name, UserRoles.description
FROM user
LEFT JOIN UserRoles
ON user.id_user_roles = UserRoles.id_user_roles WHERE user.id = p;
UPDATE user SET last_login = lastlogin WHERE user.id = p;
INSERT INTO login_info (id_user, timestamp)
VALUES (p, lastlogin);
SET s = 1;
ELSE
SELECT login_error_times INTO counter FROM user WHERE email = email1;
IF (counter < 3) THEN
SET counter = counter + 1;
UPDATE user
SET login_error_times = counter
WHERE user.email = email1;
SET s = 0;
ELSE
UPDATE user
SET locked = 1
WHERE user.email = email1;
SET s = 2;
END IF;
END IF;
END

Related

SQL Rollback not going back to last commit

I'm having problem with the following in SQL:
SELECT
*
FROM
department_dup
ORDER BY dept_no;
Then I ran this piece of code:
COMMIT;
UPDATE department_dup
SET
dept_no = 'd011',
dept_name = 'Quality Control'
;
ROLLBACK;
SELECT * from department_dup;
But the table is not going back to the last commit
Can anyone please tell me what's going wrong here? Thanks!
By default, MySQL starts the session for each new connection with autocommit enabled, so MySQL does a commit after each SQL statement if that statement did not return an error
Option 1: Set autocomit off
SET autocommit = 0
OPtion 2: Use transaction boundaries.
START TRANSACTION;
UPDATE department_dup
SET
dept_no = 'd011',
dept_name = 'Quality Control'
;
ROLLBACK;

SQL Event - DELETE AND UPDATE rows on tables after UPDATE other table

I'd like to have a tricky SQL statement as an Event that runs every couple of minutes.
Currently, I'm doing so with Java, using 3 separate statements that executing sequentiality in a transaction connection.
Q: I don't know how to construct such an SQL statement without Java. If impossible to have a single SQL statement, I'd like to use transaction (as I'm using in Java) and rollback in case of failure in any of those separate statements.
My Case:
I have 3 tables: "Factory", "Plan", "Machine".
I want to do something as below:
1.
WHERE Machines.annualCheck == "TRUE"
SET Machine.status = "IN_ANNUAL_CHECK"
For machines that got updated I need to do the following:
2.1 Update the related factory
WHERE Factory.id == Machine.linkFactoryID
UPDATE Factory.totalActiveMachines = --1
2.2 Delete the upcoming plans that planned to be handled by the related machine
DELETE rows WHERE Plan.willHandleByMachineID = Machine.ID
p.s. I'm using MySQL
Thank you!
Update:
In following to Simonare suggestion, I tired to do the following:
DELIMITER $
CREATE PROCEDURE annualCheck(IN Machine_ID int, IN Factory_ID int)
BEGIN
UPDATE machine_table
SET machine_table.annualCheck = 'IN_ANNUAL_CHECK'
WHERE machine_table.machine_id = Machine_ID;
UPDATE factory_table
SET factory_table.totalActiveMachines = factory_table.totalActiveMachines - 1
WHERE factory_table.factory_id = Factory_ID;
DELETE FROM plan_table WHERE plan_table.assign_to_machine = Machine_ID
END$
DELIMITER $$
BEGIN
SELECT #m_id = machine_id, #f_id = link_factory_id
FROM machine_table
WHERE machine_table.annualCheck = 'TRUE';
END$$
CALL annualCheck(#m_id,#f_id)
I don't know why, but I'm running into syntax errors - one after the other.
It's my first time to use PROCEDURE and DELIMITER. Am I doing it right?
you can use stored procedure
delimiter //
CREATE PROCEDURE myProc (IN Machine_ID int)
BEGIN
UPDATE myTable
SET Machine.status = "IN_ANNUAL_CHECK"
WHERE Machines.annualCheck == "TRUE";
Update the related factory
WHERE Factory.id == Machine.linkFactoryID
UPDATE Factory.totalActiveMachines = totalActiveMachines -1;
DELETE FROM Plan WHERE Plan.willHandleByMachineID = Machine_ID;
END//
then you can execute it either from mysql
CALL simpleproc(#a);
or from Java
It is also possible to create trigger on the Machine table, something like this:
CREATE TRIGGER `TRG_Machines_AfterUpdate` AFTER UPDATE ON `Machine` FOR EACH ROW BEGIN
IF OLD.annualCheck = 'TRUE' AND NEW.annualCheck = 'IN_ANNUAL_CHECK' THEN
UPDATE
Factory
SET
totalActiveMachines = totalActiveMachines - 1
WHERE
id = NEW.linkFactoryID
;
DELETE FROM
Plan
WHERE
willHandleByMachineID = NEW.ID
;
END;
END
So you can just issue normal update:
UPDATE Machine SET annualCheck = 'IN_ANNUAL_CHECK' WHERE annualCheck = 'TRUE'

MySQL atomic sequence number generator

My program require to generate UNIQUE transaction Id through MySQL due to multiple machine environment.
I using the following MySQL function, I google review it is not atomic as I think it is.
DELIMITER $$
CREATE DEFINER=`` FUNCTION `getNextTXID`(dummy INT) RETURNS int(11)
DETERMINISTIC
BEGIN
DECLARE txid bigint(20);
SET txid = (SELECT next_txid FROM txid_seq LIMIT 1 FOR UPDATE);
IF txid > 15000000 THEN
SET txid = 0;
END IF;
UPDATE txid_seq SET next_txid = txid + 500 LIMIT 1;
RETURN txid;
END
I previously using last_insert_id, but a new requirement to reset after 15M number was imposed. I cannot reproduce a race condition any 2 of 100 Processes actually get the same transaction number (in a batch of 500, if application used up all 500, get again).
Question:
Does this function atomic
Any other way of doing it correctly?
Table : MyISAM
Storage Engine : MyISAM
Auto commit : TRUE
Edit:
I am using MySQL C API.
Thanks in advance for any apply.
It's not atomic, because you're reading and writing separately. But this should do the same operation atomically, while still returning the value from LAST_INSERT_ID():
UPDATE txid_seq SET next_txid = LAST_INSERT_ID((next_txid * (next_txid <= 15000000)) + 500) LIMIT 1;

MySql Transaction Success Table Lock, Last Insert

I have a few questions I am unsure of and would be a great help if someone could help me
1) I want to return to my code that the user was successful inserted into the database it nor a fail. Would I do this along the lines of If Last_Insert_id is not null? return a message saying inserted.
2) Will last_isert_id be particular to the user inserted, i.e. one insert at a time. Do i need to do a lock to achieve this. I.e. if i had a profile table for instance and i got last_isert_id i could guarantee if many ppl are signing up at once that each id would be for each user. Or do i need to do table locking.
Any other feedback most welcome in terms of improvements
BEGIN
DECLARE _user_role_permission int;
DECLARE _user_id int;
DECLARE EXIT HANDLER FOR SQLEXCEPTION ROLLBACK;
DECLARE EXIT HANDLER FOR SQLWARNING ROLLBACK;
SET _user_role_permission = (SELECT id FROM user_role_permission WHERE role_permission = in_role_permission);
START TRANSACTION;
INSERT INTO user_site(username, email, status, password, password_strategy, salt, requires_new_password, reset_token,
login_time, lock_status, login_ip, created_ip, activation_key, validation_key,
create_time, update_time)
VALUES(in_username, in_email, in_status, in_password,
in_password_strategy, in_salt, in_requires_new_password,
in_reset_token, in_login_time, in_lock_status, in_login_ip,
in_created_ip, in_activation_key, in_validation_key,
in_create_time, in_update_time);
SET _user_id = LAST_INSERT_ID();
INSERT INTO user(user_site_id) VALUES(_user_id);
INSERT INTO user_permission(user_id, permission_id)VALUES (_user_id,_user_role_permission);
COMMIT;
END
You can use SELECT ##warning_count; and SELECT ##error_count; after INSERT to see if it succeeded. I yes, it will return 0 rows.
last_insert_id() is limited to the current connection, so no matter how many concurrent connections are there to database, you will always get correct value.

MySQL CREATE FUNCTION fails on a shared webserver

I have the following function. When I try to create it on a webserver, it fails with
You do not have the SUPER privilege and binary logging is enabled (you might want to use the less safe log_bin_trust_function_creators variable)
I never had the same problem with any other webhosting (and the exact same function), how can I fix this? (I can't change MySQL settings).
CREATE FUNCTION `has_action_access`(in_role VARCHAR(255), in_request VARCHAR(255), in_action VARCHAR(255)) RETURNS tinyint(1)
READS SQL DATA
DETERMINISTIC
BEGIN
DECLARE current_role VARCHAR(255);
DECLARE found, cont TINYINT(1) DEFAULT 0;
SET current_role = in_role;
findattempt: REPEAT
SELECT COUNT(*) FROM cyp_action_access WHERE request = in_request AND action = in_action AND role = current_role INTO found;
IF found = 0 THEN
SELECT COUNT(*) FROM cyp_roles WHERE name = current_role INTO cont;
IF cont = 1 THEN
SELECT inherits FROM cyp_roles WHERE name = current_role INTO current_role;
END IF;
END IF;
UNTIL (cont = 0 OR found > 0) END REPEAT;
RETURN found;
END;
MySQL server's version is 5.0.90-log.
Your user does not have super privilege. You will need to contact your webhosting provider and have them update this. If they will not grant you that option ask them to execute the script for you.