user157195
user157195

Reputation:

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?

Upvotes: 7

Views: 15658

Answers (6)

Troy Morehouse
Troy Morehouse

Reputation: 5435

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.

Upvotes: 1

Chad N B
Chad N B

Reputation: 867

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

  1. Use SERIALIZABLE transaction isolation level. However note that session 2 will block on the INSERT.
  2. 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.
  3. 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).

Upvotes: 6

Kalle
Kalle

Reputation: 11

Do a select statement. While looping through it, create a list string of unique IDs. Then pass this list back to mySQL using IN.

Upvotes: 1

wuputah
wuputah

Reputation: 11395

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:

Upvotes: 5

user183037
user183037

Reputation: 2579

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.

Upvotes: -1

CoolEsh
CoolEsh

Reputation: 3154

There is no single query solution. Use

select * from MyTable t where _critera_
//get the resultset and then
delete from MyTable where _critera_

Upvotes: 0

Related Questions