Minimize Row Locking in an Update with Subqueries - mysql

I've got a SP (Percona 5.6) which is doing an Update with 3 Subselects. Each Subquery is doing a Count with different filters. So basically the SP is
UPDATE table1
SET column1 = (SELECT count FROM table2 ...),
SET column2 = (SELECT count FROM table2 ...),
SET column3 = (SELECT count FROM table2 ...);
I detect some Deadlocks when the SP is running and some Rows in table2 gets updated.
Therefore I try to figure out how to minimize the Row Locking Time for table2 during the SP. What happens if I pull out the Subqueries, eg
SET #count1 = (SELECT count FROM table2 ...);
SET #count2 = (SELECT count FROM table2 ...);
SET #count3 = (SELECT count FROM table2 ...);
UPDATE table1
SET column1 = #count1,
SET column2 = #count2,
SET column3 = #count3;
I'm aware that this can cause inconsistency, but in this case this is ok. Will this change something regarding the locking time? I'm not sure if the whole SP is running as one transaction. Will this change the performance?

Try SET tx_isolation = READ-UNCOMMITTED for the duration of the SP.
Is SELECT count... really SELECT COUNT(*) ... WHERE ... and it hits lots of rows? Or is it just hitting one row?
Is there only one row in table1? Or did you leave off a WHERE clause?
(I am worried that you over-simplified the question; this could lead to getting irrelevant advice.)

Related

MySQL RDS Stored Procedure Update query is slow

I have an update query in the Stored Procedure that updates TABLE1 based on the IDs present from TABLE2. This is written using a subquery as follows.
update TABLE1 A
set status = 'ABC'
where A.ID in (
select ID
from TABLE2 B
where B.SE_ID = V_ID
and B.LOAD_DT = V_DT
);
I have rewritten this using,
a JOIN
masking the subquery from the main query
Used a temp table and join.
Standalone update is faster.
But placing this in the Stored Procedure is very slow.
TABLE1 needs to be updated with 2000 records from the 2000 ID from TABLE2.
Someone please help on this.
Avoid using subqueries in place of joins. MySQL optimizes this use of subquery very poorly. That is, it may run the subquery up to 2000 times.
Use a join:
UPDATE TABLE1 A
INNER JOIN TABLE2 B
ON A.ID = B.ID
SET A.status = 'ABC'
WHERE B.SE_ID = V_ID
AND B.LOAD_DT = V_DT;
You'll want to create an index to optimize this.
ALTER TABLE TABLE2 ADD INDEX (SE_ID, LOAD_DT, ID);
No need to create an index on TABLE1, if my assumption is correct that its ID column is its primary key. That is itself an index.

mysql - insert results of query into same table with joins

I use this site all the time to find answers to my questions but this is the first time I've ask one.
I have two tables and want to query both tables and insert the results into two columns on table 1. Something like this:
SELECT a.column1 from table1 a LEFT Join ( SELECT 'column1' from 'table2' ) AS a ON where a.column1 like '%column1.table2%';
Basically then insert the result into column5 and column6 on table 1
I know that this isn't correct as it doesn't work and it's not going to update any thing. For testing I'm running select statements to verify before running the update command. Another way of saying what I need would be:
If column1 in table1 is like column1 in table2 then update column5 in table1 with corresponding entry from column2 in table2 and update column6 in table1 with column7 from table1 with corresponding entry in column3 from table2;
I realize that this is not the best explanation but that is the best way I can explain what I want. Please ask questions if more information is needed and I will do my best to explain.
Thanks for any input you have.
In MySQL you can do multi-table updates in a single update statement using joins in the table list:
update t1 inner join t2 on t1.column1 like concat('%',t2.column1, '%')
set t1.column5=t2.column2, t1.column6=t2.column7
However, using like is not necessarily the best idea, since more than 1 records from t2 may match the same record within t1, therefore a single t1 record may be updated several times during a single run.
Your description of which t1 fields should be updated by which field from t2 is inaccurate, cannot really tell the logic there.

Mysql how to do this without locking

I want to query one table to see whether there exist any rows with 'A' type.
so I use this sql:
SELECT EXISTS(select * from %T where type = 'A');
then I need to update another table's column value to the above result. In order to prevent an insert with 'A' type happen during update, I am thinking to use a lock. but lock is very expensive, is there other alternative way to do this without locking?
If we have to use lock, I am thinking if table has already had type A, there is no need to lock insert during update because the result will still be 1. only prevent insert when there is no row with type A. How to do that?
Thanks!
Since you want to update a table when there is a type 'A', normally I would suggest you do the update or check every time right after inserting or deleting a row.
Or you could do something like this:
UPDATE table1 t1
LEFT JOIN table2 t2 ON t1.type = t2.type
SET t1.column1 = 1
WHERE t2.type IS NOT NULL
or
UPDATE table1 t1
SET t1.column1 = 1
WHERE EXISTS (
SELECT ...
FROM table2
WHERE type = 'A'
);
Please give me more details such as what is your table like and what exactly you want to do so we can discuss further.

could a rows lock made with IN(,,,) generate dead locks?

My goal is to avoid dead locks and so I centralized all locks in the same place ordering by table name and then by ID ascending:
SELECT * FROM table1 WHERE ID = 1 FOR UPDATE
SELECT * FROM table1 WHERE ID = 2 FOR UPDATE
SELECT * FROM table1 WHERE ID = 3 FOR UPDATE
SELECT * FROM table1 WHERE ID = 4 FOR UPDATE
SELECT * FROM table2 WHERE ID = 1 FOR UPDATE
SELECT * FROM table2 WHERE ID = 2 FOR UPDATE
SELECT * FROM table2 WHERE ID = 3 FOR UPDATE
SELECT * FROM table2 WHERE ID = 4 FOR UPDATE
but I wonder if I can do the same using IN() (which is probably a bit faster)
SELECT * FROM table1 WHERE ID IN(1,2,3,4) FOR UPDATE
SELECT * FROM table2 WHERE ID IN(1,2,3,4) FOR UPDATE
will the rows be locked in the exact order specified by the IN() operand or the lock will be applied using the "natural table ordering" instead?
ID is a primary auto_increment field in all tables and I don't "reuse" old deleted IDs (so in theory the natural ordering should always be ascending)
thanks in advance!
added the update:
UPDATE table1 SET t1="hello1" WHERE ID = 1;
UPDATE table1 SET t1="hello2" WHERE ID = 2;
UPDATE table1 SET t1="hello3" WHERE ID = 3;
UPDATE table1 SET t1="hello4" WHERE ID = 4;
UPDATE table2 SET t2="hello1" WHERE ID = 1;
UPDATE table2 SET t2="hello2" WHERE ID = 2;
UPDATE table2 SET t2="hello3" WHERE ID = 3;
UPDATE table2 SET t2="hello4" WHERE ID = 4;
...
COMMIT;
Even though it's a little unclear but some part of the answer to your question is stated in the MySQL's documentation:
expr IN (value,...)
Returns 1 if expr is equal to any of the values in the IN list, else
returns 0. If all values are constants, they are evaluated according
to the type of expr and sorted. The search for the item then is done
using a binary search.
Here's what you should get of it: If all values in the list are constants, they are compared sorted using binary search.
So in the end, it doesn't matter if you have sorted the values or not, because MySQL will sort them even if they are not. Nevertheless this wasn't your question. Now let's get back to your question.
First of all, deadlocks are absolutely possible in MySQL when your are using InnoDb and they happen all the time (at least to me). The strategy you've chosen to prevent deadlocks is a valid one (acquiring locks according to some order). But unfortunately I don't think it's going to work in MySQL. You see, even though in your query it is clearly stated which records you want to be locked, but the truth is that they are not the only records that will be locked:
A locking read, an UPDATE, or a DELETE generally set record locks on
every index record that is scanned in the processing of the SQL
statement. It does not matter whether there are WHERE conditions in
the statement that would exclude the row. InnoDB does not remember the
exact WHERE condition, but only knows which index ranges were scanned.
The locks are normally next-key locks that also block inserts into the
“gap” immediately before the record. However, gap locking can be
disabled explicitly, which causes next-key locking not to be used.
So it's hard to say which records are actually locked. Now consider MySQL is searching the index for the first value in your list. As I've just said few more records might be locked along the way while MySQL is scanning the index. And since scanning indices does not happen in order (or at least that's what I believe), records would get locked regardless of their order. Which means that deadlocks are not prevented.
The last part is my own understanding of the situation and I've actually never read that anywhere before. But in theory it sounds right. Yet I really would like someone to prove me wrong (just so I can trust MySQL even more).
Ok so this question is not really that clear as to what you want... so this may not be an answer to your question.. but I made some test stuff to help you visualize the data and how IN() works.. so I hope thats at least helpful.
SETUP:
CREATE TABLE table1
(`id` int, `username` varchar(10), `t1` varchar(55));
INSERT INTO table1
(`id`, `username`, `t1`)
VALUES
(4, 'John', 'Hi1'),
(3, 'Ram ', 'Hi2'),
(2, 'Jack', 'Hi3'),
(1, 'Jill', 'Hi4');
CREATE TABLE table2
(`id` int, `username` varchar(10), `t1` varchar(55));
INSERT INTO table2
(`id`, `username`, `t1`)
VALUES
(1, 'Joe', 'Hey1'),
(2, 'Fes', 'Hey2'),
(3, 'Ned', 'Hey3'),
(4, 'Abe', 'Hey4');
I made table1 have a backwards ID.. aka 4, 3, 2, 1 and then table2 has the regular incremented id. 1, 2, 3, 4...
1. SELECT * FROM table1
RESULT-OF-1
2. SELECT * FROM table2
RESULT-OF-2
3. SELECT * FROM table1 WHERE id = 1 OR id = 2 OR id = 3 OR id = 4 same result as 1
4. SELECT * FROM table1 WHERE id IN(1, 2, 3, 4).. same result as 1.
5. SELECT * FROM table1 WHERE id IN(1, 4, 3, 2).. same result as 1.
IN() compares the id from each row in the table to what is specified inside the IN() statement.. if it matches it will return the row.. so it returns the data in the "natural table ordering" .. more info HERE
there is a way to do the update you posted with an IN() statement.. without writing out each update.
UPDATE table1 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UPDATE table2 SET t2= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
all you do here is combine the 'hello' string with the ID and set your column to it since that is what you posted. I hope that helps understand how the data gets pulled out.
output for the two updates : table1.... table2
for locking tables to update you should probably lock them to WRITE and UNLOCK to prevent a deeplock. see post
LOCK TABLES table1 WRITE, table2 WRITE;
UPDATE table1 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UPDATE table2 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UNLOCK TABLES;
Rows are locked in the order they are read, so no order is guaranteed. Even if you add an ORDER BY clause, the rows will be locked as they are read, not as they are ordered. Here is another good question with some great answers

MySQL update query on two tables but update only one table

I have two tables:
Table1: group_name, item_name, status
Table2: group_name, geo
I want to update table1. The default status is 0. I want to update the status of table1 to 1 using a single UPDATE statement.
I want to check for each row in table1 if the group_name exists in table2. If so, I will update status to 1.
I tried this but was not able to get the correct result.
UPDATE table1
SET table1.`STATUS`=1
WHERE table2.group_name=table1.group_name
How can I achieve my desired result?
you can use multiple update table syntax, so your query would be:
UPDATE table1,table2
SET table1.`STATUS`=1
WHERE table2.group_name=table1.group_name
You could use a multi-table update as others have shown. But you can also do it in a slightly simpler way with a single table update statement with a subselect:
UPDATE table1
SET STATUS = 1
WHERE group_name IN (SELECT group_name FROM table2)
Note also that you don't even need the precondition that status in all rows is initially set to zero. You can update all rows to their correct values in a single UPDATE statement:
UPDATE table1
SET STATUS = group_name IN (SELECT group_name FROM table2)
the SET is his command... the actual format is :
update "tablename"
set "columnname" =
"newvalue"
[,"nextcolumn" =
"newvalue2"...]
where "columnname"
OPERATOR "value"
[and|or "column"
OPERATOR "value"];
don't have a SQL database up and running, but I believe the UPDATE command is similar to the FROM command, so i think you have to do
UPDATE table1, table2 SET table1.'status' = 1
WHERE table2.group_name=table1.group_name