执行以下命令,在MySQL CLI中,在一个新的连接上执行,不需要做特别的准备(并且要特别注意执行时间):
ysql> show create table t \G
*************************** 1. row ***************************
Table: t
Create Table: CREATE TABLE `t` (
`a` int(10) unsigned NOT NULL,
`b` int(10) unsigned NOT NULL,
PRIMARY KEY (`a`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> select * from t;
Empty set (5.20 sec)
mysql> select count(*) from t;
+----------+
| count(*) |
+----------+
| 0 |
+----------+
1 row in set (5.22 sec)
mysql> select * from t where a = 10;
Empty set (0.00 sec)
mysql> select * from t where a < 10;
Empty set (5.35 sec)
mysql> select * from t where a > 10;
Empty set (5.41 sec)
mysql> select * from t where a in (10, 20, 30);
Empty set (0.00 sec)
mysql> select * from t where a > 1000000;
Empty set (0.00 sec)
mysql> select * from t where a > 500000;
Empty set (2.60 sec)
发生了什么?为什么这么慢?为什么有些东西慢而有些不慢?
SHOW PROCESSLIST没有显示任何异常:
mysql> show processlist;
+----+------+-----------------+------+---------+------+-------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------------+------+---------+------+-------+------------------+
| 6 | root | localhost:34840 | test | Query | 0 | init | show processlist |
| 8 | root | localhost:34842 | test | Sleep | 2116 | | NULL |
+----+------+-----------------+------+---------+------+-------+------------------+
2 rows in set (0.00 sec)
如果我们看一下显示引擎INNODB的状态,就会发现一条线索:一个忙于作恶的事务:
---TRANSACTION 1481, ACTIVE 2183 sec
3487 lock struct(s), heap size 570920, 2001743 row lock(s), undo log entries 11000000
MySQL thread id 8, OS thread handle 0x7fca27ba2700, query id 5000144 localhost 127.0.0.1 root cleaning up
它还显示在information_schema.innodb_trx:
mysql> select * from information_schema.innodb_trx \G
*************************** 1. row ***************************
trx_id: 1481
trx_state: RUNNING
trx_started: 2014-04-17 01:38:27
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 11003487
trx_mysql_thread_id: 8
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 0
trx_lock_structs: 3487
trx_lock_memory_bytes: 570920
trx_rows_locked: 2001743
trx_rows_modified: 11000000
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 10000
trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.01 sec)
我使用下面的脚本插入一百万行,然后每一行更新十次,保持事务打开和未提交:
#!/usr/bin/env ruby
require "mysql"
m = Mysql.new("127.0.0.1", "root", "", "test", 13000)
m.query("DROP TABLE IF EXISTS t")
m.query("CREATE TABLE t (a INT UNSIGNED NOT NULL, b INT UNSIGNED NOT NULL, PRIMARY KEY (a)) ENGINE=InnoDB")
m.query("START TRANSACTION")
(1..1000000).each do |i|
m.query("INSERT INTO t (a, b) VALUES (#{i}, 0)")
puts "Inserted #{i} rows..." if i % 10000 == 0
end
(1..10).each do |i|
m.query("UPDATE t SET b=#{i}")
puts "Updated #{i} times..."
end
sleep 1000000
正如InnoDB撤销日志记录和历史系统的基础中所描述的,这些修改都是对数据库进行的,未提交的写事务和我的读事务都使用单一的索引结构。尽管我的读事务不能看到写入事务插入和随后修改的任何行,但为了弄清楚这一点,它需要将所有的undo记录应用到遇到的每一行(每行10条)。当然,这需要时间。
您可以看到不同操作所花费的时间中,飞行中数据“泄漏”的一些隐藏可见性。例如,扫描所有行的时间超过5秒,但是使用条件a > 1000000可以立即计算。令人怀疑的是,> 500000的条件大约只需要扫描所有行的一半时间(因为它只需要做一半的工作)。当然,所有这些查询都返回一个空集。
虽然几乎没有人会故意做我的脚本所做的事情,但我以前在生产系统中确实看到过这样的结果:一个非常大的事务多次更新一行会导致试图访问该表的许多慢速查询。有些查询会很快,有些则很慢。
undo历史记录的形式对用户可以使用的空间没有限制。我已经提交了MySQL Bug #72362:“用户可以无限制地使用undo space”,建议添加配置选项来限制用户可能消耗的undo space。我写了以下内容: 由于InnoDB目前已经实现,用户可以通过以下任意一种方式使用无限的空间来撤消历史记录: