mysql深入三-锁


以下基于innodb存储引擎。

一.问题
1.锁标记在哪?
在索引上。
更新,删除操作加x锁。
如果update条件是主键,则只会加在主键对应聚簇索引上。
如果update条件是唯一索引,则会先在对应唯一索引加锁,然后再在主键索引上加锁。

2.锁类型
大致分x锁,及s锁。
对于RR隔离级别的还有gap锁,gap锁只是为了防止幻读。

3.意向锁作用
分为共享意向锁is及排它意向锁ix。
意向锁用于解决当需要加表级锁时,高效判断是否当前表有某些行被锁定。
首先问题是在于:如果事务a对某行加了x锁,事务b需要对表加表级锁,这时如果事务b加成功了就出现问题了,因为事务a还可以对数据进行修改。
所以要保证数据的完整性,则需要在加表级锁的时候判断当前是否有事务对表中某些行加了排它锁。
比较粗暴的方法是扫描表看是否有行被加了排它锁,显然性能低。
因此就有了意向锁,在申请排它锁时必须要先申请意向ix锁。然后表锁如果看到当前表存在意向锁,就阻塞直到对应拥有意向锁的事务提交。

4.gap锁作用
当要锁定的条件不是唯一索引就会发生gap锁。
防止在加gap锁区间插入数据。
  READ COMMITTED隔离级别不会出现gap锁。

只有在repeatable read隔离级别出现,并且相应要有加共享或排它锁的范围语句。
请求给一个不存在的记录加锁,也会加gap锁,eg: select * from table where id=‘10’,记录10不存在。

select * from t3 where id>='1' and id<'10' for update;
这条语句会加gap锁及对应满足条件记录的行级x锁,即next-key锁。

test表中的v1字段值可以划分的区间为:
(-∞,1)
(1,3)
(3,4)
(4,5)
(5,7)
(7,9)
(9, +∞)
假如要更新v1=7的数据行,那么此时会在索引idx_v1对应的值,也就是v1的值上加间隙锁,锁定的区间是(5,7)和(7,9)。同时找到v1=7的数据行的主键索引和非唯一索引,对key加上锁。

禁用gap锁:
隔离级别是:RC或者禁用: innodb_locks_unsafe_for_binlog

5.next-key锁
行锁和间隙锁组合起来就叫Next-Key Lock。
select * from t3 where id>='1' and id<'10' for update;
这条语句会加next-key锁。

+-------------+-------------+-----------+-----------+-------------+-----------------+------------+-----------+----------+------------------------+
| lock_id     | lock_trx_id | lock_mode | lock_type | lock_table  | lock_index      | lock_space | lock_page | lock_rec | lock_data              |
+-------------+-------------+-----------+-----------+-------------+-----------------+------------+-----------+----------+------------------------+
| 5576:71:3:1 | 5576        | X         | RECORD    | `test`.`t3` | GEN_CLUST_INDEX |         71 |         3 |        1 | supremum pseudo-record |
| 5575:71:3:1 | 5575        | X         | RECORD    | `test`.`t3` | GEN_CLUST_INDEX |         71 |         3 |        1 | supremum pseudo-record 

6.当前读与快照读
快照读不加锁:
select * from table where id='1';
当前读需要加锁:
select * from table where id='1' in share mode;
select * from table where id='1' for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;

7.在RC级别下,若update table where id=‘1’,id不是索引,会加什么锁?
最终在x锁,分两步:
存储层会返回并加锁后所有记录,扫描的是聚簇索引,锁也是加上索引上,然后在mysql server层进行过滤。
因此会先对所有记录在锁,然后再unlock。

二.实践

一定要设置当前自动提交事务为false.
show variables like 'autocommit';
set autocommit=0;

1.锁
事务a:
start TRANSACTION;
select * from t3 where id='1' for update;

事务b:
update t3 set name='t3change' where id='1' ;

如果事务a不提交事务,意味着一直持有id='1'这行排它锁,最后事务b会出现以下错误:
Error : Lock wait timeout exceeded; try restarting transaction

如果出现某个服务慢,其中涉及更新操作有可能是数据库层锁。
可以用以下命令查看是否存在锁等待。

先查看是否有事务等待锁:
mysql> select * from information_schema.INNODB_locks;
+-------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+--
| lock_id     | lock_trx_id | lock_mode | lock_type | lock_table  | lock_index | lock_space | lock_page | lock_rec | lock_data         |
+-------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+--
| 5543:71:5:2 | 5543        | X         | RECORD    | `test`.`t3` | idx        |         71 |         5 |        2 | 1, 0x000000000202 |
| 5542:71:5:2 | 5542        | X         | RECORD    | `test`.`t3` | idx        |         71 |         5 |        2 | 1, 0x000000000202 |
+-------------+-------------+-----------+-----------+-------------+------------+------------+-----------+----------+-------------------+
2 rows in set, 1 warning (0.00 sec)

能够得到的信息是事务5543和5542有竞争行级锁,同一条记录,lock_data相同。
然后看对应事务详细信息。

mysql> select * from information_schema.INNODB_trx;
+--------+-----------+---------------------+-----------------------+---------------------+------------+-----------------
| trx_id | trx_state | trx_started         | trx_requested_lock_id | trx_wait_started    | trx_weight | trx_mysql_thread_id | trx_query                                   | trx_operation_state | trx_tables_in_use | trx_tables_locked | trx_lock_structs | trx_lock_memory_bytes | trx_rows_locked | trx_rows_modified | trx_concurrency_tickets | trx_isolation_level | trx_unique_checks | trx_foreign_key_checks | trx_last_foreign_key_error | trx_adaptive_hash_latched | trx_adaptive_hash_timeout | trx_is_read_only | trx_autocommit_non_locking |
+--------+-----------+---------------------+-----------------------+---------------------+------------+-----------------
| 5543   | LOCK WAIT | 2017-09-03 19:12:48 | 5543:71:5:2           | 2017-09-03 19:12:48 |          2 |                1868 | update  t3 set name='t4change' where id='1' | starting index read |                 1 |                 1 |                2 |                  1136 |               1 |                 0 |                       0 | REPEATABLE READ     |                 1 |                      1 | NULL                       |                         0 |                         0 |                0 |                          0 |
| 5542   | RUNNING   | 2017-09-03 19:12:44 | NULL                  | NULL                |          4 |                1865 | NULL                                        | NULL                |                 0 |                 1 |                4 |                  1136 |               3 |                 0 |                       0 | REPEATABLE READ     |                 1 |                      1 | NULL                       |                         0 |                         0 |                0 |                          0 |
2 rows in set (0.00 sec)

事务5543处于阻塞状态,5542处于运行状态。


三.锁查看语句

在information_schema下面有三张表:INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS,通过这三张表,可以更简单地监控当前的事务并分析可能存在的问题。

四.其它命令
可以用以下命令查看有哪些sql在运行:
show processlist;

设置innodb锁超时时间:
innodb_lock_wait_timeout,默认120秒

四.死锁问题
产生主要是两个事务锁资源顺序不一致。
解决,消除不一致锁顺序。
比如有业务场景,需要更新多个用户记录,而又要求一个事务。
这时可以根据用户id进行排序进行更新。
产生死锁后,mysql会根据事务对应权重将权重小的给回滚掉。
开启死锁日志:set global innodb_print_all_deadlocks = 1;

如果发生死锁,可以用以下命令:
show mysql engine innodb
来查看

RR隔离级别下的:
delete from table where id=‘1’ 锁获取三种情况:
1.找到delete对应符合条件的记录,这时加x mode no gap记录锁
2.未找到符合条件记录的锁,加gap锁
3.找到符合条件,但记录在索引中标记为已删除,这时加next-key锁
死锁例子:
RR隔离级别,语句为:delete from table where name=‘a’;
其中name为唯一索引。
存在三个事务执行上述:delete from table where name=‘a’;
事务1加x锁,事务2在此时也加x锁。
这时事务1完成对于这条记录删除并释放x锁。
事务3也执行这条语句,但看到对应记录在索引上已标记为删除,因此加的是next-key锁,但是此时事务2拥有x锁,因此事务3需要等待事务2释放x锁。
事务2在持有x锁的情况下,想要进行删除但发现记录已标识为删除,因此也对该记录加next-key锁。
由于mysql公共锁的特性,因此事务2需要等事务3获取到并释放next-key锁后才能获取到next-key。
因此就产生了死锁。

总结下要产生这类死锁的条件:
1.隔离级别为RR
2.三个以上事务进行delete from table where name=‘a’操作
3.innodb



猜你喜欢

转载自blog.csdn.net/zhaozhenzuo/article/details/77838540