LOCK IN SHARE MODE locks entire table - mysql

Documentation:
SELECT ... LOCK IN SHARE MODE sets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.
However, some experimentation suggests that it locks more than the rows that are read.
CREATE TABLE example (a int);
START TRANSACTION;
SELECT a FROM example WHERE a = 0 LOCK IN SHARE MODE;
And then on another connection
INSERT INTO example VALUES (1);
The later connection blocks on the lock.
It would seems that LOCK IN SHARE MODE locks more than "any rows that are read".
What exactly does LOCK IN SHARE MODE lock?

Make sure you have an index on the a column. Otherwise, in order to evaluate WHERE a = 0, it has to read every row in the table, and it will then set a lock on each row as it reads it.
ALTER TABLE example ADD INDEX (a);

Related

Why MySQL InnoDB also acquire Gap locks for update/delete operation?

As far as I know the gap lock is used to prevent phantom read, and I found gap lock is set by locking read in most articles via Google search.
A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record. For example, SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE; prevents other transactions from inserting a value of 15 into column t.c1, whether or not there was already any such value in the column, because the gaps between all existing values in the range are locked.
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-gap-locks
I guess this (set gap lock on locking read) is sufficient. Why update, delete also set gap lock.
UPDATE ... WHERE ... sets an exclusive next-key lock on every record the search encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.
https://dev.mysql.com/doc/refman/8.0/en/innodb-locks-set.html
And another issue is what happened if there is no suitable index where gap lock can be attached?
Does fall back to lock on the entire table?
Here we assumed that using the default transaction isolation level Repeatable Read.
It depends on the conditions in your SELECT, UPDATE, or DELETE. They set gap locks to prevent other concurrent sessions from adding rows to the set that would be matched by the conditions.
In InnoDB, locking statements always lock the most recent committed row versions. So they don't really obey the REPEATABLE READ snapshot. They act more like READ-COMMITTED.
Therefore, if you do a statement like this:
UPDATE FROM MyTable SET ... WHERE created_at > '2020-03-22';
It must lock the gap following the highest value of created_at, which will prevent other sessions from adding new rows.
This is to simulate REPEATABLE READ, to make sure that if you run the same UPDATE again, it will affect the same rows, and it won't accidentally affect new rows.

Unable to apply read or write lock manually into Innodb table via mysql command line

I am trying to test a scenario in table locked cases.
I tried to apply read and write locks like these -
mysql> lock tables table_name write;
Query OK, 0 rows affected (0.01 sec)
But was able to write after this -
mysql> insert into table_name (id) values (1000000023);
Query OK, 1 row affected (0.00 sec)
Read lock is also not working. I was able to do a select query successfully after applying lock.
I need to test my application behaviour (restful APIs) when we do a table migration of large amount of data, when there may be write locks applied on the table. So, I guess, if I am able to set the lock via command line, it will apply to my APIs as well. I am using the same user to login to command line as I am using in my application to login to mysql.
Please let me know if I am missing something.
The table is InnoDb type.
I think you are misunderstanding table locking. According to the documentation:
MySQL enables client sessions to acquire table locks explicitly for the purpose of cooperating with other sessions for access to tables, or to prevent other sessions from modifying tables during periods when a session requires exclusive access to them. A session can acquire or release locks only for itself. One session cannot acquire locks for another session or release locks held by another session.
You are basically locking other sessions out of writing to the table, but YOU can still write to it while you hold the lock.
For what you are doing, there is an example in that documentation page:
The correct way to use LOCK TABLES and UNLOCK TABLES with transactional tables, such as InnoDB tables, is to begin a transaction with SET autocommit = 0 (not START TRANSACTION) followed by LOCK TABLES, and to not call UNLOCK TABLES until you commit the transaction explicitly. For example, if you need to write to table t1 and read from table t2, you can do this:
SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;
When you call LOCK TABLES, InnoDB internally takes its own table lock, and MySQL takes its own table lock. InnoDB releases its internal table lock at the next commit, but for MySQL to release its table lock, you have to call UNLOCK TABLES. You should not have autocommit = 1, because then InnoDB releases its internal table lock immediately after the call of LOCK TABLES, and deadlocks can very easily happen. InnoDB does not acquire the internal table lock at all if autocommit = 1, to help old applications avoid unnecessary deadlocks.

How to use this sql add lock in the rows?

SQL:
INSERT LOW_PRIORITY IGNORE INTO A_new (??) SELECT ?? FROM A FORCE INDEX(`PRIMARY`)
WHERE ((`id` >='XX' )) AND ((`id` <= 'XX')) LOCK IN SHARE MODE;
Is it possible to add S Locks in the range rows without any transaction?
If not,does this sql only work in transaction?
I get the answer from mysql document.
INSERT INTO T SELECT ... FROM S WHERE ... sets an exclusive index
record lock (without a gap lock) on each row inserted into T. If the
transaction isolation level is READ COMMITTED, InnoDB does the search
on S as a consistent read (no locks). Otherwise, InnoDB sets shared
next-key locks on rows from S. InnoDB has to set locks in the latter
case: During roll-forward recovery using a statement-based binary log,
every SQL statement must be executed in exactly the same way it was
done originally.

Why commit does not cause deadlock

I have read mysql reference manual about the InnoDB locks. I got that :
(from:
http://dev.mysql.com/doc/refman/5.5/en/innodb-locks-set.html)
INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.
If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row. Suppose that an InnoDB table t1 has the following structure:
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
Now suppose that three sessions perform the following operations in order:
Session 1:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 2:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 3:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 1:
ROLLBACK;
The first operation by session 1 acquires an exclusive lock for the row. The operations by sessions 2 and 3 both result in a duplicate-key error and they both request a shared lock for the row. When session 1 rolls back, it releases its exclusive lock on the row and the queued shared lock requests for sessions 2 and 3 are granted. At this point, sessions 2 and 3 deadlock: Neither can acquire an exclusive lock for the row because of the shared lock held by the other.
I made a experiment and I found is is the fact.
My question is :
(1)I found that, if I commit session 1, and deadlock did not happen. Why? When I commit session 1, The X row lock will also be released. So I do not understand.
Thx.
I suspect this is an edge case that is not worth making more efficient. Note that you had to get 3 sessions trying to grab the same row -- a rare happening. And you did a ROLLBACK -- also rare. So the deadlock that occurred is overkill, but not worth fixing. For this reason, one must be prepared to handle deadlocks everywhere.
FYI, If this were 3 nodes of a Galera cluster, there would be errors on COMMIT that the code would have to handle. I suspect there are even more combinations of strange things going on if you apply this transaction (with rollback or commit) multiple times to multiple nodes.
Now to your question... Presumably the deadlock did not happen because one of the threads got the exclusive lock and the other was hit with a "wait" instead of a "deadlock".

Mysql:when query this sql:"select * from user limit 0,1000",if allow delete operation

I have a mysql lock question:
If I query this sql: select * from user order by id asc limit 0,1000.
Then anohther thread simutanousely delete the row between 0,1000 in the user table,if allowed?
In the MySQL Documentation for InnoDB, it states InnoDB does locking on the row level and runs queries as nonlocking consistent reads by default.
More directly, however is Internal Locking Methods, which says MySQL uses table-level locking for MyISAM, MEMORY, and MERGE tables, allowing only one session to update those tables at a time. Also, this:
MySQL grants table write locks as follows:
1. If there are no locks on the table, put a write lock on it.
2. Otherwise, put the lock request in the write lock queue.
MySQL grants table read locks as follows:
1. If there are no write locks on the table, put a read lock on it.
2. Otherwise, put the lock request in the read lock queue.
Okay, let's digest that: In InnoDB, each row has it's own lock, which means your query would loop through the table until it hit a row that has a lock. However, in MyISAM, there is only one lock for the entire table, which is set before the query is executed.
In other words, for InnoDB, if the DELETE operation removed the row before the SELECT operation read the row, then the row would not show up in the results. However, if the SELECT operation read the row first, then it would be returned in the result set, but any future SELECT operations would not show the row. If you want to intentionally lock the entire result set in InnoDB, look into SELECT ... FOR UPDATE.
In MyISAM, the table is locked by default, so it depends which query began execution first: if the DELETE operation started first, then the row would not be returned with the SELECT. But if the SELECT operation began execution first, then the row would indeed be returned.
There is more about interlaced here: http://dev.mysql.com/doc/refman/5.0/en/select.html
And also here: Any way to select without causing locking in MySQL?