| Bug #33278 | ANALYZE TABLE in InnoDB holds exclusive locks on subsequent execution | ||
|---|---|---|---|
| Submitted: | 16 Dec 2007 22:51 | Modified: | 7 Apr 15:52 |
| Reporter: | Morgan Tocker | ||
| Status: | Verified | ||
| Category: | Server: Locking | Severity: | S3 (Non-critical) |
| Version: | 5.0-bk, 5.1-bk | OS: | Any |
| Assigned to: | Davi Arnaut | Target Version: | 5.1+ |
| Triage: | Triaged: D2 (Serious) / R3 (Medium) / E5 (Major) | ||
[17 Dec 2007 11:49]
Mark Leith
The backtrace for the ANALYZE TABLE thread that is waiting is:
Thread 11 (process 201 thread 0x3303):
#0 0x900247e7 in semaphore_wait_signal_trap ()
No symbol table info available.
#1 0x900287d4 in pthread_cond_wait ()
#2 0x00094864 in wait_for_refresh ()
#3 0x0009a87d in open_table ()
#4 0x0009b027 in open_tables ()
#5 0x0009b29b in open_and_lock_tables ()
#6 0x00118249 in mysql_admin_table ()
#7 0x001192f6 in mysql_analyze_table ()
#8 0x00079bcb in mysql_execute_command ()
#9 0x0007a655 in mysql_parse ()
#10 0x0007b091 in dispatch_command ()
#11 0x0007bf01 in do_command ()
#12 0x0007ca4b in handle_one_connection ()
#13 0x90024147 in _pthread_body ()
If you look in open_table(), the only way that we can get to wait_for_refresh() is:
if (table->s->version != refresh_version)
{
/*
** There is a refresh in progress for this table
** Wait until the table is freed or the thread is killed.
*/
close_old_data_files(thd,thd->open_tables,0,0);
if (table->in_use != thd)
wait_for_refresh(thd);
Session 2 within the *first* ANALYZE TABLE is changing the table's internal version
(table->s->version), meaning that it needs to be reloaded in to the table cache. This can
not be done until the first session (the long running SELECT COUNT(*)) has completed.
So all subsequent ANALYZE TABLE commands (and indeed most probably other DML such as
INSERT, UPDATE etc.) will have to wait for the first session to complete, so that the
table can be reloaded in to the table cache.
I believe the question now is - does ANALYZE TABLE *really* need to update the table's
internal version number?
[17 Dec 2007 14:53]
Heikki Tuuri
This is probably a bug in MySQL's table locking code. ANALYZE TABLE in InnoDB just refreshes the table statistics. I do not know if the table version number should be changed every time we refresh the stats!
[27 Feb 2008 15:34]
Konstantin Osipov
This should actually be fixed in 5.1 by a patch for Bug#12713. Needs re-verification.
[27 Feb 2008 15:34]
Konstantin Osipov
The patch for 12713 will make it into the main tree only later this week.
[6 Mar 2008 17:52]
Susanne Ebrecht
It's still at bk tree of version 5.0 from 2008-03-05 23:30 CET
CET == UTC+1
mysql> select version()\G
*************************** 1. row ***************************
version(): 5.0.60-debug
I needed some more rows because otherwise my "select count()" was too fast.
I can repeat exact the same behaviour then Morgan by using 13*10^6 rows.
Making a "select count()" and two times Analyze at a second connection and making
insert/update statements at a third connection and "show processlist" at a fourth
connection:
Using insert statement from above:
mysql> show processlist\G
*************************** 1. row ***************************
Id: 1
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 26
State: Sending data
Info: SELECT count(*) FROM my_innodb_test
*************************** 2. row ***************************
Id: 2
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 22
State: Waiting for table
Info: ANALYZE TABLE my_innodb_test
*************************** 3. row ***************************
Id: 3
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 14
State: Waiting for table
Info: insert into my_innodb_test (id, a) values (NULL, 'test!')
*************************** 4. row ***************************
Id: 4
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 0
State: NULL
Info: show processlist
Using update statement from above:
mysql> select version()\G
*************************** 1. row ***************************
version(): 5.0.60-debug
1 row in set (0.00 sec)
mysql> show processlist\G
*************************** 1. row ***************************
Id: 1
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 12
State: Sending data
Info: SELECT count(*) FROM my_innodb_test
*************************** 2. row ***************************
Id: 2
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 8
State: Waiting for table
Info: ANALYZE TABLE my_innodb_test
*************************** 3. row ***************************
Id: 3
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 4
State: Waiting for table
Info: update my_innodb_test set a = 'test!' where id = 10
*************************** 4. row ***************************
Id: 4
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 0
State: NULL
Info: show processlist
Also ít's still at bk tree of version 5.1 from 2008-03-06 02:30 CET
mysql> select version()\G
*************************** 1. row ***************************
version(): 5.1.24-rc-debug
mysql> show processlist\G
*************************** 1. row ***************************
Id: 1
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 10
State: Sending data
Info: SELECT count(*) FROM my_innodb_test
*************************** 2. row ***************************
Id: 2
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 6
State: Waiting for table
Info: ANALYZE TABLE my_innodb_test
*************************** 3. row ***************************
Id: 3
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 3
State: Waiting for table
Info: insert into my_innodb_test (id, a) values (NULL, 'test!')
*************************** 4. row ***************************
Id: 4
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 0
State: NULL
Info: show processlist
4 rows in set (0.16 sec)
mysql> show processlist\G
*************************** 1. row ***************************
Id: 1
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 12
State: Sending data
Info: SELECT count(*) FROM my_innodb_test
*************************** 2. row ***************************
Id: 2
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 4
State: Waiting for table
Info: ANALYZE TABLE my_innodb_test
*************************** 3. row ***************************
Id: 3
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 2
State: Waiting for table
Info: update my_innodb_test set a = 'test!' where id = 10
*************************** 4. row ***************************
Id: 4
User: miracee
Host: localhost
db: miracee
Command: Query
Time: 0
State: NULL
Info: show processlist
[7 Mar 2008 18:13]
Konstantin Osipov
ANALYZE TABLE updates table statistics. After ANALYZE table completed, we must purge all old open tables from the table cache, to ensure that the new statistics is available to SQL statements. Currently we do not do this "online" - that is, the way a purge must happen is that we can't have old and new versions of the same table in the cache. Until this is implemented, the bug can not be fixed.

Description: Analyze table seems to (for no good reason) hold an exclusive lock on the second execution (see how to repeat). How to repeat: -- Step 1. Create a sample table, load a lot of same rows. 3263442 should do on my system. DROP TABLE IF EXISTS my_innodb_test; CREATE TABLE my_innodb_test ( id INT NOT NULL auto_increment PRIMARY KEY, a char(255) NOT NULL) ENGINE=InnoDB; INSERT INTO my_innodb_test (id, a) VALUES (NULL, REPEAT('a', 255)); INSERT INTO my_innodb_test (id, a) SELECT NULL, a.a FROM my_innodb_test a, my_innodb_test b; INSERT INTO my_innodb_test (id, a) SELECT NULL, a.a FROM my_innodb_test a, my_innodb_test b; INSERT INTO my_innodb_test (id, a) SELECT NULL, a.a FROM my_innodb_test a, my_innodb_test b; INSERT INTO my_innodb_test (id, a) SELECT NULL, a.a FROM my_innodb_test a, my_innodb_test b; INSERT INTO my_innodb_test (id, a) SELECT NULL, a.a FROM my_innodb_test a, my_innodb_test b; -- Step 2. Issue an expensive SELECT query. SELECT count(*) FROM my_innodb_test; -- Step 3. While step 2 is still running, in another session run this: ANALYZE TABLE my_innodb_test; ANALYZE TABLE my_innodb_test; -- Results: The first ANALYZE is almost instant, the second waits for step 2 to complete! -- Now run an insert in a third session to prove that the analyze is an exclusive lock: insert into my_innodb_test (id, a) values (NULL, 'test!'); -- Note: Any statement blocks, not just inserts (not related to the autoincrement lock): update my_innodb_test set a = 'test!' where id = 10; -- Results in show processlist should be something like this: mysql> show processlist; +----+------+-----------+------+---------+------+-------------------+-----------------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------+------+---------+------+-------------------+-----------------------------------------------------------+ | 1 | root | localhost | test | Query | 235 | Sending data | select count(*) from my_innodb_test | | 3 | root | localhost | test | Query | 81 | Waiting for table | analyze table my_innodb_test | | 4 | root | localhost | test | Query | 23 | Waiting for table | insert into my_innodb_test (id, a) values (NULL, 'test!') | | 5 | root | localhost | NULL | Query | 0 | NULL | show processlist | +----+------+-----------+------+---------+------+-------------------+-----------------------------------------------------------+ 4 rows in set (0.02 sec) Suggested fix: It seems like the query is held up in the SQL Layer. It's technically possible to figure out cardinality from MVCC, so InnoDB should be allowed to do that.