SELECT … FOR UPDATE如何影响INNODB的锁级别
isvx2809
8年前
<h2>SELECT … FOR UPDATE如何影响INNODB的锁级别</h2> <p>如果 SELECT ... FOR UPDATE 生效,需要在noautocommit的环境下,即 BEGIN;COMMIT/ROLLBACK; 或者 SET AUTOCOMMIT=0 的前提下。本文使用 BEGIN;COMMIT/ROLLBACK; 创造noautocommit的环境研究 SELECT ... FOR UPDATE 对于INNODB的锁级别影响。</p> <p>约定</p> <ul> <li> <p>表结构如下</p> <pre> <code class="language-sql">CREATE TABLE `t` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(16) NOT NULL, `num1` int(11) NOT NULL, `num2` int(11) NOT NULL, `num3` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `ux_name` (`name`), KEY `ix_num1_num2` (`num1`,`num2`) ) ENGINE=InnoDB AUTO_INCREMENT=67 DEFAULT CHARSET=utf8</code></pre> </li> <li> <p>表数据如下</p> <pre> <code class="language-sql">select * from t; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 1 | AAAAA | 0 | 2 | 2 | | 2 | BBBBB | 1 | 2 | 2 | | 3 | CCCCC | 0 | 0 | 0 | | 4 | DD | 4 | 1 | 1 | | 5 | EE | 0 | 5 | 5 | | 66 | FFFFF | 0 | 5 | 5 | +----+-------+------+------+------+ 6 rows in set (0.00 sec)</code></pre> </li> </ul> <h3>实验</h3> <p>一、WHERE条件使用主键</p> <pre> <code class="language-sql"># session 1 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> explain select * from t where id=1; +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | t | const | PRIMARY | PRIMARY | 4 | const | 1 | NULL | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 1 row in set (0.00 sec) mysql> select * from t where id=1 for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 1 | AAAAA | 0 | 2 | 2 | +----+-------+------+------+------+ 1 row in set (0.00 sec) # session 2 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where id=2 for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 2 | BBBBB | 1 | 2 | 2 | +----+-------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where name="DD" for update; +----+------+------+------+------+ | id | name | num1 | num2 | num3 | +----+------+------+------+------+ | 4 | DD | 4 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where name="AAAAA" for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where num1=0 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where num3=2 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction</code></pre> <p>二、WHERE条件使用唯一索引</p> <pre> <code class="language-sql"># session 1 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> explain select * from t where name='AAAAA' ; +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | t | const | ux_name | ux_name | 50 | const | 1 | NULL | +----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+ 1 row in set (0.00 sec) # session 2 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where name ="DD" for update; +----+------+------+------+------+ | id | name | num1 | num2 | num3 | +----+------+------+------+------+ | 4 | DD | 4 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where id=2 for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 2 | BBBBB | 1 | 2 | 2 | +----+-------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where name='AAAAA' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where num1=0 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where num3=2 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction</code></pre> <p>三、WHERE条件使用普通索引</p> <pre> <code class="language-sql"># session 1 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> explain select * from t where num1=0 and num2=5; +----+-------------+-------+------+---------------+--------------+---------+-------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+--------------+---------+-------------+------+-------+ | 1 | SIMPLE | t | ref | ix_num1_num2 | ix_num1_num2 | 8 | const,const | 2 | NULL | +----+-------------+-------+------+---------------+--------------+---------+-------------+------+-------+ 1 row in set (0.00 sec) mysql> select * from t where num1=0 and num2=5 for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 5 | EE | 0 | 5 | 5 | | 66 | FFFFF | 0 | 5 | 5 | +----+-------+------+------+------+ 2 rows in set (0.00 sec) # session 2 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where id=1 for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 1 | AAAAA | 0 | 2 | 2 | +----+-------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where name="AAAAA" for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 1 | AAAAA | 0 | 2 | 2 | +----+-------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where num1=0 and num2=5 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where num2=5 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where num3=5 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction</code></pre> <p>四、WHERE条件使用联合索引的前缀索引</p> <pre> <code class="language-sql"># session 1 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> explain select * from t where num1=1 ; +----+-------------+-------+------+---------------+--------------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+--------------+---------+-------+------+-------+ | 1 | SIMPLE | t | ref | ix_num1_num2 | ix_num1_num2 | 4 | const | 1 | NULL | +----+-------------+-------+------+---------------+--------------+---------+-------+------+-------+ 1 row in set (0.00 sec) mysql> select * from t where num1=1 for update; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 2 | BBBBB | 1 | 2 | 2 | +----+-------+------+------+------+ 1 row in set (0.00 sec) # session 2 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where id =3; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 3 | CCCCC | 0 | 0 | 0 | +----+-------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where id =3 for update -> ; +----+-------+------+------+------+ | id | name | num1 | num2 | num3 | +----+-------+------+------+------+ | 3 | CCCCC | 0 | 0 | 0 | +----+-------+------+------+------+ 1 row in set (0.00 sec) mysql> select * from t where num1=4 for update; -- 使用了普通索引 +----+------+------+------+------+ | id | name | num1 | num2 | num3 | +----+------+------+------+------+ | 4 | DD | 4 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) mysql> explain select * from t where num1=4 for update; +----+-------------+-------+------+---------------+--------------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+--------------+---------+-------+------+-------+ | 1 | SIMPLE | t | ref | ix_num1_num2 | ix_num1_num2 | 4 | const | 1 | NULL | +----+-------------+-------+------+---------------+--------------+---------+-------+------+-------+ 1 row in set (0.00 sec) mysql> select * from t where num1=0 for update; -- 使用了全表扫描 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> explain select * from t where num1=0 for update; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t | ALL | ix_num1_num2 | NULL | NULL | NULL | 6 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec)</code></pre> <p>五、WHERE条件不使用索引</p> <pre> <code class="language-sql"># session 1 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> explain select * from t where num3=1 ; +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ | 1 | SIMPLE | t | ALL | NULL | NULL | NULL | NULL | 6 | Using where | +----+-------------+-------+------+---------------+------+---------+------+------+-------------+ 1 row in set (0.00 sec) mysql> select * from t where num3=1 for update; +----+------+------+------+------+ | id | name | num1 | num2 | num3 | +----+------+------+------+------+ | 4 | DD | 4 | 1 | 1 | +----+------+------+------+------+ 1 row in set (0.00 sec) # session 2 mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where id=1 for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction mysql> select * from t where name='BBBBB' for update; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction</code></pre> <h3>总结</h3> <ol> <li>WHERE条件使用主键, SELECT ... FOR UPDATE 为行级锁;</li> <li>WHERE条件使用唯一索引, SELECT ... FOR UPDATE 为行级锁;</li> <li>WHERE条件使用普通索引, SELECT ... FOR UPDATE 为行级锁;</li> <li>WHERE条件使用联合索引的前缀索引, SELECT ... FOR UPDATE 为行级锁;</li> <li>WHERE条件不使用索引, SELECT ... FOR UPDATE 为表级锁;</li> </ol> <p>即:WHERE条件能使用索引时, SELECT ... FOR UPDATE 表现为行级锁;WHERE条件不使用索引, SELECT ... FOR UPDATE 表现为表级锁;</p> <p> </p> <p>来自:https://github.com/wing324/helloworld_zh/blob/master/MySQL/SELECT … FOR UPDATE如何影响INNODB的锁级别.md</p> <p> </p>