MySQL record UPDATE should fail but does not. Why? - mysql

here is an interesting situation.
I start a transaction with MySQL. My transaction involves 3 related queries.
Each query must succeed, and if not then none should be written to the database.
Now... on purpose, for the 2nd query...which happens to be an UPDATE query... I changed
the pk value identifying the record to be updated to an invalid (non-existing) PK value. I wanted the 2nd query to fail for testing purposes. The query is fine, it is just that the c_id value is wrong (the record I'm trying to UPDATE does not exits).
The problem is that the query is executed with an "OK"...
mysql> UPDATE tableX SET bal = 4576.99 WHERE c_id = 3789;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
This is a problem because the error (is error from my perspective since a key record that must be updated was not updated in a chain of related queries) was not caught and the transaction thus did not abort and rollback, instead the process goes on to the 3rd query which also succeeds and then the transaction is committed.
So, I find it strange that such an error is not caught by MySQL or not labeled an error by MySQL.
Any insights as to why or how to fix?

It is correct, 0 rows were updated.
If, for your logic, that is an error you should test the number of affected rows and then raise an error if that number is 0:
DECLARE count INT;
UPDATE tableX SET bal = 4576.99 WHERE c_id = 3789;
SELECT ROW_COUNT() INTO count;
IF count = 0 THEN
CALL raise_error;
END IF;
error will make the transaction rollback.
To raise an error just call a routine which doesn't exist as explained on this SO question:
How to raise an error within a MySQL function
further info about row_count():
http://dev.mysql.com/doc/refman/5.0/en/information-functions.html#function_row-count

Related

MySQL update returns 0 rows affected while select returns results

select * from t_circle
where status = 0 and author_phone = 13511111111
and id in (
1,
2,
3
)
;
returns 3 rows with status of 0.
But the following update query with same conditions returns 0 rows affected:
udpate t_circle set status = 2
where status = 0 and author_phone = 13511111111
and id in (
1,
2,
3
);
Is there a reason for this? I have tried start transaction and commit, but still 0 rows affected. Other questions' answers suggest to run select first and make sure the rows are changed since if new value == old value, it is considered affected. I have excluded these 2 possibilities.
Note: this issue is found in our production server(Yeah I know I shouldn't but I have to) with InnoDB engine. I could modify the contents in GUI client like DBeaver and click save and the changes take effect, but not with sql statements. I wonder if it has anything to do with my account authorization?
Resolved! It is because I misspelled UPDATE. When I use mysql> in command line, update with 'udpate' just gives Query OK, 0 rows affected. My mysql's version is 5.7
I know that there is something wrong with the user permission of your client
By comparing GUI generated sql queries when I change something in GUI and my own queries. I found out the reason is I misspelled update to udpate. Now I begin to wonder how could mysql not complain about syntax error and just returns with 0 rows affected?
Well, at least now I know what to do when something odd happens.

SQL database command not updating

I am trying to execute this:
UPDATE WORKS_ON
SET Hours = 5.0
WHERE Essn=99988777 AND Pno=10;
and it threw an error,
19:07:29 UPDATE WORKS_ON SET Hours=5.0 WHERE Essn=99988777 AND Pno=10 Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column To disable safe mode, toggle the option in Preferences -> SQL Queries and reconnect. 0.000 sec
I did what it asks and reconnected, now there is no error but doesn't do anything
19:36:26 UPDATE WORKS_ON SET Hours= 5.0 WHERE Essn=99988777 AND Pno=10 0 row(s) affected Rows matched: 0 Changed: 0 Warnings: 0 0.000 sec
I want all Hours for people that have Essn=999887777 and Pno=10. Hours will change to 5.0
I hope you have already unchecked safe updates in preferences.
From what you have posted it looks like there are no matching rows where Essn=99988777 and Pnp=10 at the same time.If you can provide sample data and expected results it would be better
First set SQL_SAFE_UPDATES=0, It’s because you try to update a table without a WHERE that uses a KEY column, if you use PRIMARY KEY then it would not create that kind of problem, let's try this way
SET SQL_SAFE_UPDATES=0;
UPDATE WORKS_ON
SET Hours = 5.0
WHERE Essn=99988777 AND Pno=10;
EDIT
you can modify your query to follow the rule (use PRIMARY KEY on WHERE clause), if it doesn't work, then precede the (table name with the schema name) like
UPDATE your_schemaname.WORKS_ON
SET Hours = 5.0
WHERE Essn=99988777 AND Pno=10;

Deadlock mysql multiple batch update operations

I am getting following exception when I try to do a batch update. There are multiple threads running at same time which might be accessing a row in database. I am doing multiple batch updates. Can anyone please comment on relation between size of batch and deadlock ? By decreasing the batch size (currently batch size = 1000), will the probability of deadlock decrease ?
The exception I am getting is
com.mysql.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
Short answer:
yes, the probability would decrease
Long answer:
Lets figure out why the deadlocks are occurring. When you update a row an exclusive lock is set on this particular row and it will be held until your transaction is commited/rolled back.
That means, no other transaction may update it — it would just block until the transaction is finished. A deadlock would occur when tran1 is willing to lock rows being held by tran2, and tran2, in turn, is already waiting for some rows locked by tran1
Here's an example:
MariaDB [test]> create table a (id int primary key, value int);
Query OK, 0 rows affected (0.14 sec)
MariaDB [test]> insert into a values (1, 0), (2, 0), (3, 0), (4, 0);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
mysql console 1:
step 1> start transaction;
step 3> update a set value = 1 where id = 2;
step 5> update a set value = 1 where id = 1;
mysql console 2:
step 2> start transaction;
step 4> update a set value = 1 where id = 1;
step 6> update a set value = 1 where id = 2;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
The more rows are being touched(=updated) during every batch update, the higher the probability of such kind of conflicts is.
You might lower this probability by traversing the rows in a well-defined order. In this case the simple example I've provided wouldn't be feasible.
More details on avoiding deadlocks are in this awesome article:
http://www.xaprb.com/blog/2006/08/03/a-little-known-way-to-cause-a-database-deadlock/

How to Test Select for Update in MySQL

I am performing SELECT ... FOR UPDATE or row level locking with InnoDB tables.
My intention is to only one request can read the same row. So if two users make request for the same data as the same time. Only one of them get data, who fires the query first.
But How can i test that locking is placed or not. as I am testing it by retrieving the same data at same time and both users getting the data.
Note: My tables are InnoDB, My query executes in transaction, my query as below:
SELECT * FROM table_name WHERE cond FOR UPDATE;
Any other thing I have to check for this to make work?
open 2 mysql client session.
on session 1:
mysql> start transaction;
mysql> SELECT * FROM table_name WHERE cond FOR UPDATE;
... (result here) ...
1 row in set (0.00 sec)
on session 2:
mysql> start transaction;
mysql> SELECT * FROM table_name WHERE cond FOR UPDATE;
... (no result yet, will wait for the lock to be released) ...
back to session 1, to update selected record (and release the lock):
mysql> UPDATE table_name SET something WHERE cond;
mysql> commit;
back to session 2:
1) either showing lock timeout error
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
2) or showing result
... (result here) ...
1 row in set (0.00 sec)
3) or showing no result (because corresponding record has been modified, so specified condition was not met)
Empty set (0.00 sec)
You can use own lock mechanizm with lock_by column.
UPDATE table_name SET locked_by=#{proccess_id} WHERE cond and locked_by IS NULL
Now in your program you will get count of affected rows:
if(affected_rows==0)
return 'rows locked'
else
//do your staff with locked_by=#{process_id} rows
With this mechanism you can control locked rows and locking processes. You can also add in UPDATE statement locked_at=NOW() to get more info about locked row.
Don't forget to add some index on locked_by column.
Here is MySQL docs about working with locks.
Before update you can put lock, releasing it after. In another transaction you can check lock using it unique name. Strategy for naming you can choose yourself.

mysql select for delete

Edit:
I found a solution here http://mysql.bigresource.com/Track/mysql-8TvKWIvE/
assuming select takes a long time to execute, will this lock the table for a long time?
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT foo FROM bar WHERE wee = 'yahoo!';
DELETE FROM bar WHERE wee = 'yahoo!';
COMMIT;
I wish to use a criteria to select the rows in mysql, return them to my app as resultset, and then delete these rows. How can this be done? I know I can do the following but it's too inefficient:
select * from MyTable t where _critera_.
//get the resultset and then
delete from MyTable t where t.id in(...result...)
Do I need to use a transaction? Is there a single query solution?
I needed to SELECT some rows by some criteria, do something with the data, and then DELETE those same rows atomically, that is, without deleting any rows that meet the criteria but were inserted after the SELECT.
Contrary to other answers, REPEATABLE READ is not sufficient. Refer to Consistent Nonlocking Reads. In particular note this callout:
The snapshot of the database state applies to SELECT statements within a transaction, not necessarily to DML statements. If you insert or modify some rows and then commit that transaction, a DELETE or UPDATE statement issued from another concurrent REPEATABLE READ transaction could affect those just-committed rows, even though the session could not query them.
You can try it yourself:
First create a table:
CREATE TABLE x (i INT NOT NULL, PRIMARY KEY (i)) ENGINE = InnoDB;
Start a transaction and examine the table (this will be called session 1 now):
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM x;
Start another session (session 2) and insert a row. Note this session is in auto commit mode.
INSERT INTO x VALUES (1);
SELECT * FROM x;
You will see your newly inserted row. Then back in session 1 again:
SELECT * FROM x;
DELETE FROM x;
COMMIT;
In session 2:
SELECT * FROM x;
You'll see that even though you get nothing from the SELECT in session 1, you delete one row. In session 2 you will see the table is empty at the end. Note the following output from session 1 in particular:
mysql> SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Query OK, 0 rows affected (0.00 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM x;
Empty set (0.00 sec)
/* --- insert in session 2 happened here --- */
mysql> SELECT * FROM x;
Empty set (0.00 sec)
mysql> DELETE FROM x;
Query OK, 1 row affected (0.00 sec)
mysql> COMMIT;
Query OK, 0 rows affected (0.06 sec)
mysql> SELECT * FROM x;
Empty set (0.00 sec)
This testing was done with MySQL 5.5.12.
For a correct solution
Use SERIALIZABLE transaction isolation level. However note that session 2 will block on the INSERT.
It seems that SELECT...FOR UPDATE will also do the trick. I have not studied the manual 100% in depth to understand this but it worked when I tried it. The advantage is you don't have to change the transaction isolation level. Again, session 2 will block on the INSERT.
Delete the rows individually after the SELECT. Basically you'd have to include a unique column (the primary key would be good) in the SELECT and then use DELETE FROM x WHERE i IN (...), or something similar, where IN contains a list of keys from the SELECT's result set. The advantage is you don't need to use a transaction at all and session 2 will not be blocked at any time. The disadvantage is that you have more data to send back and forth to the SQL server. Also I don't know if deleting the rows individually is as efficient as using the same WHERE clause as the original SELECT, but if the original SELECT's WHERE clause was complicated or slow the individual deletion may well be faster, so that could be another advantage.
To editorialize, this is one of those things that is so dangerous that even though it's documented it could almost be considered a "bug." But hey, the MySQL designers didn't ask me (or anyone else, apparently).
Do I need to use a transaction? Is there a single query solution?
Yes, you need to use a transaction. You cannot delete and select rows in a single query (i.e., there is no way to "return" or "select" the rows you have deleted).
You don't necessarily need to do the REPEATABLE READ option - I believe you could also select the rows FOR UPDATE, although this is a higher level of locking. REPEATABLE READ does seem to be the lowest level of locking you could use to execute this transaction safely. It happens to be the default for InnoDB.
How much this affects your table depends on whether you have an index on the wee column or not. Without it, I believe MySQL would have to lock writes the entire table.
Further reading:
Wikipedia - Isolation (database systems)
http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html
http://dev.mysql.com/doc/refman/5.0/en/innodb-locking-reads.html
Do a select statement. While looping through it, create a list string of unique IDs. Then pass this list back to mySQL using IN.
You could select your rows into a temporary table, then delete using the same criteria as your select. Since SELECT FROM WHERE FOR UPDATE also returns a result set, you could alter the SELECT FOR UPDATE to a SELECT INTO tmp_table FOR UPDATE. Then delete your selected rows, either using your original criteria, or by using the data in the temporary table as the criteria.
Something like this (but haven't checked it for syntax)
START TRANSACTION;
SELECT a,b into TMP_TABLE FROM table_a WHERE a=1 FOR UPDATE;
DELETE FROM table_a
USING table_a JOIN TMP_TABLE ON (table_a.a=TMP_TABLE.a, table_a.b=TMP_TABLE.b)
WHERE 1=1;
COMMIT;
Now your records are gone from the original table, but you also have a copy in your temporary table, which you can keep, or delete.
There is no single query solution. Use
select * from MyTable t where _critera_
//get the resultset and then
delete from MyTable where _critera_
Execute the SELECT statement with the WHERE clause and then use the same WHERE clause in the DELETE statement as well. Assuming there was no interim changes to the data, the same rows should be deleted.
EDIT: Yes, you could set this up as a single transaction so there's no modification to the tables while you're doing this.