Any way to select without causing locking in MySQL?
Asked Answered
A

10

173

Query:

SELECT COUNT(online.account_id) cnt from online;

But online table is also modified by an event, so frequently I can see lock by running show processlist.

Is there any grammar in MySQL that can make select statement not causing locks?

And I've forgotten to mention above that it's on a MySQL slave database.

After I added into my.cnf:transaction-isolation = READ-UNCOMMITTED the slave will meet with error:

Error 'Binary logging not possible. Message: Transaction level 'READ-UNCOMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'' on query

So, is there a compatible way to do this?

Ascus answered 27/5, 2009 at 19:43 Comment(1)
For others that encounter this question and are having a hard time with the locks on their tables: How mySQL uses locks internally depends on the storage engine. Read the answer by @zombat below.Capuche
A
213

Found an article titled "MYSQL WITH NOLOCK"

https://web.archive.org/web/20100814144042/http://sqldba.org/articles/22-mysql-with-nolock.aspx

in MS SQL Server you would do the following:

SELECT * FROM TABLE_NAME WITH (nolock)

and the MYSQL equivalent is

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
SELECT * FROM TABLE_NAME ;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ ;

EDIT

Michael Mior suggested the following (from the comments)

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;
SELECT * FROM TABLE_NAME ;
COMMIT ;
Adlay answered 27/5, 2009 at 21:27 Comment(8)
Just a note to future readers that you may wish to eliminate SESSION and thus have the transaction level apply only to the next transaction. Then, simply replace the third statement above with COMMIT. This will be a noop in this case, but have a side-effect of ending the transaction and resetting to the default isolation level.Garb
Just a note, that link is dead... :(Limestone
Sorry, but I have to downvote this answer for not mentioning the very important differences between InnoDB and MyISAM here. As stated by @Ascus above, this will work for InnoDB but not for MyISAM tables.Capuche
@Craig It certainly is inaccurate that MyISAM is not issuing READ locks during SELECT queries - there are locks and opposing to InnoDB those locks are table locks, blocking all requested WRITE locks and all subsequent queries during execution. The original question appears to be about InnoDB though and isolation levels are non-existent for MyISAM too - the docs for the SET TRANSACTION statement state: "This statement sets the transaction isolation level, used for operations on InnoDB tables."Tartary
Point conceded. :-) I was really trying to refer to the locking behavior of MyISAM vs InnoDB. These isolation level-based solutions do not apply to MyISAM, which is not transactional, so it uses a simple table lock. MyISAM UPDATE and DELETE have to wait for the table lock to clear, so any subsequent SELECT's queue up behind the write request, blocked until the write finishes. MyISAM has no "dirty reads" and no way to allow most writes to happen concurrently with reads, thus no point griping about any comments here "failing to address MyISAM." I think that's what I was getting at. :-)Preach
Just curious - will ROLLBACK also reset isolation level to default, the same as COMMIT does, and is this specific to MySQL? Can I use MySQL syntax if I want to be compatible also with MS SQL Server?Occipital
pretty ugly syntax imho for a frequent operation..how hard is it to add a NOLOCK?Thor
Just a note: the last EDIT is wrong. From the documentation: Without any SESSION or GLOBAL keyword, the statement applies to the next (not started) transaction performed within the current session. Subsequent transactions revert to using the SESSION isolation level.Biotechnology
T
33

If the table is InnoDB, see http://dev.mysql.com/doc/refman/5.1/en/innodb-consistent-read.html -- it uses consistent-read (no-locking mode) for SELECTs "that do not specify FOR UPDATE or LOCK IN SHARE MODE if the innodb_locks_unsafe_for_binlog option is set and the isolation level of the transaction is not set to SERIALIZABLE. Thus, no locks are set on rows read from the selected table".

Thom answered 27/5, 2009 at 19:50 Comment(2)
Hi @Alex Martelli reading is always allowed NO MATTER if it is an EXCLUSIVE LOCK in the row, right? I'm with level READ UNCOMMITTED and I'm still ALLOWED to READ the row EVEN WITH EXCLUSIVE LOCK? Obviously with dirty data, but it still lets me. Is that okay, or am I missing something I don't understand? - Thanks in advaceHilliard
I'm talking about a normal read (SELECT... FROM WHERE) @Alex MartelliHilliard
M
21

Use

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED.

Version 5.0 Docs are here.

Version 5.1 Docs are here.

Citing from the linked docs: Setting transaction characteristics (as shown above) ...

Without any SESSION or GLOBAL keyword:

  • The statement applies only to the next single transaction performed within the session.

  • Subsequent transactions revert to using the session value of the named characteristics.

  • The statement is not permitted within transactions

Meier answered 27/5, 2009 at 19:51 Comment(1)
Thank you,I think it's near,but how long will this statement take affect? I'm going to use this statement in a PHP programme,and should be best reset TRANSACTION ISOLATION LEVEL automatically once query finishedAscus
H
16

You may want to read this page of the MySQL manual. How a table gets locked is dependent on what type of table it is.

MyISAM uses table locks to achieve a very high read speed, but if you have an UPDATE statement waiting, then future SELECTS will queue up behind the UPDATE.

InnoDB tables use row-level locking, and you won't have the whole table lock up behind an UPDATE. There are other kind of locking issues associated with InnoDB, but you might find it fits your needs.

Holdup answered 27/5, 2009 at 19:49 Comment(4)
Will "SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED" work for MyISAM tables?Ascus
MyISAM tables do not support transactions in any form. A transactional query will run on a MyISAM table, so the query you mention above will execute, but it has no effect.Holdup
Then what can I do to avoid SELECTS queuing up in the case of MyISAM?Ascus
what can I do to avoid SELECTS queuing up in the case of MyISAM? Switch to innodb. MyISAM uses table level locks for every query. That's it's major flaw.Savoirfaire
U
3

another way to enable dirty read in mysql is add hint: LOCK IN SHARE MODE

SELECT * FROM TABLE_NAME LOCK IN SHARE MODE; 
Untrue answered 7/6, 2013 at 5:1 Comment(2)
"If autocommit is set to 1, the LOCK IN SHARE MODE and FOR UPDATE clauses have no effect." ... and autocommit = 1 is defaultLaundryman
the downside of using lock in share mode and autocommit of 0 is that you need to put beginTransaction/commit around any modificationsVlissingen
B
2

Depending on your table type, locking will perform differently, but so will a SELECT count. For MyISAM tables a simple SELECT count(*) FROM table should not lock the table since it accesses meta data to pull the record count. Innodb will take longer since it has to grab the table in a snapshot to count the records, but it shouldn't cause locking.

You should at least have concurrent_insert set to 1 (default). Then, if there are no "gaps" in the data file for the table to fill, inserts will be appended to the file and SELECT and INSERTs can happen simultaneously with MyISAM tables. Note that deleting a record puts a "gap" in the data file which will attempt to be filled with future inserts and updates.

If you rarely delete records, then you can set concurrent_insert equal to 2, and inserts will always be added to the end of the data file. Then selects and inserts can happen simultaneously, but your data file will never get smaller, no matter how many records you delete (except all records).

The bottom line, if you have a lot of updates, inserts and selects on a table, you should make it InnoDB. You can freely mix table types in a system though.

Bowen answered 27/5, 2009 at 20:5 Comment(0)
P
1

Based on this document MySQL NOLOCK syntax , you can set the isolation level for all connections globally to uncommited which means with nolock for all select statements:

 SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED ;

This can be retrieved by running:

SELECT @@global.transaction_isolation;
Placard answered 31/12, 2022 at 14:4 Comment(0)
I
0

From this reference:

If you acquire a table lock explicitly with LOCK TABLES, you can request a READ LOCAL lock rather than a READ lock to enable other sessions to perform concurrent inserts while you have the table locked.

Illusionary answered 27/5, 2009 at 19:49 Comment(0)
B
0

SELECTs do not normally do any locking that you care about on InnoDB tables. The default transaction isolation level means that selects don't lock stuff.

Of course contention still happens.

Burris answered 27/5, 2009 at 21:30 Comment(4)
I know this post is old, but this answer is too general, and is only sometimes true. See dev.mysql.com/doc/refman/5.0/en/innodb-locks-set.html. Locks most certainly are acquired for reads, depending on the isolation level. Specifically, in this case, the poster is dealing with replicated databases and stated explicitly that he can use show processlist to actually see the locks. So it is safe to assume that there are in fact locks being taken.Preach
The answer is always true. There is of course, some locking - some internal mutexes inside innodb which are used (innodb buffer pool mutex, for example). Most users don't care about or notice these locks and they normally only contended during DDL operations (such as if you have a 16G buffer pool and do "drop table" in another thread). But it doesn't take any row-locks by default. That's what I meant. The answer was quite vague though.Burris
Always always? What if the transaction isolation level is set to serializable, or the select statement uses LOCK IN SHARE MODE and autocommit is disabled? I know many (most/all?) database servers now use snapshot isolation by default instead of true serialization, but aren't there still occasional justifications for forcing serializable reads? But it sounds like you were saying that in all remotely normal cases, the default conditions in MySQL do not cause read locks that affect other threads, so don't worry about a problem you don't have? I tried to undo my downvote, BTW. Sorry...Preach
I said "do not normally". I meant if you do a normal select (without FOR UPDATE or LOCK IN SHARE MODE) and use the default transaction isolation level. There are some valid cases for changing the isolation level, but I'd only do it on a per-session basis never the default.Burris
N
0

Here is an alternative programming solution that may work for others who use MyISAM IF (important) you don't care if an update has happened during the middle of the queries. As we know MyISAM can cause table level locks, especially if you have an update pending which will get locked, and then other select queries behind this update get locked too.

So this method won't prevent a lock, but it will make a lot of tiny locks, so as not to hang a website for example which needs a response within a very short frame of time.

The idea here is we grab a range based on an index which is quick, then we do our match from that query only, so it's in smaller batches. Then we move down the list onto the next range and check them for our match.

Example is in Perl with a bit of pseudo code, and traverses high to low.


# object_id must be an index so it's fast
# First get the range of object_id, as it may not start from 0 to reduce empty queries later on.

my ( $first_id, $last_id ) = $db->db_query_array(
  sql => q{ SELECT MIN(object_id), MAX(object_id) FROM mytable }
);

my $keep_running = 1;
my $step_size    = 1000;
my $next_id      = $last_id;

while( $keep_running ) {

    my $sql = q{ 
SELECT object_id, created, status FROM 
    ( SELECT object_id, created, status FROM mytable AS is1 WHERE is1.object_id <= ? ORDER BY is1.object_id DESC LIMIT ? ) AS is2
WHERE status='live' ORDER BY object_id DESC 
};  

    my $sth = $db->db_query( sql => $sql, args => [ $step_size, $next_id ] );

    while( my ($object_id, $created, $status ) = $sth->fetchrow_array() ) {

        $last_id = $object_id;
        
        ## do your stuff

    }

    if( !$last_id ) {
        $next_id -= $step_size; # There weren't any matched in the range we grabbed
    } else {
        $next_id = $last_id - 1; # There were some, so we'll start from that.
    }

    $keep_running = 0 if $next_id < 1 || $next_id < $first_id;
    
}



Nils answered 26/11, 2020 at 13:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.