MySQL学习之——锁(行锁、表锁、页锁、乐观锁、悲观锁等)
https://crossoverjie.top/
https://blog.csdn.net/ztx114/article/details/78410727
https://blog.csdn.net/java_best/article/details/54708690
表锁更适用于以查询为主,只有少量按索引条件更新数据的应用;
行锁更适用于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用。
MyISAM表锁
MyISAM存储引擎只支持表锁。
mysql> show status like 'table%'
-> ;
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Table_locks_immediate | 100 |
| Table_locks_waited | 0 |
| Table_open_cache_hits | 0 |
| Table_open_cache_misses | 0 |
| Table_open_cache_overflows | 0 |
+----------------------------+-------+
5 rows in set (0.00 sec)
Table_locks_immediate 空闲表锁个数
Table_locks_waited 锁争用个数。如果该值较高,并且有性能问题,应该首先优化查询,然后拆分表或使用复制。
表级锁:表共享读锁和表独占写锁。
读读可并行。
写会阻塞其他用户对同一表的读和写操作。
何时加表锁
myisam 在select前,会自动给涉及的所有表加读锁,在执行更新操作,会自动给涉及的表加写锁。
给表显示加锁,一般是为了在一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。
mysql不支持锁升级??,lock后,只能访问显示加锁的表,不能访问未加锁的表;同时,读锁只能执行查询操作,不能执行更新操作。在自动加锁的情况下也基本如此,myisam总是一次获得sql语句所需要的全部锁。这也正是myisam不会出现死锁的原因。??(一次性获得全部锁,破坏了死锁的请求和保持条件)
使用lock tables时,不仅需要一次锁定用到的所有表,而且,别名也需要锁定
并发插入
在一定条件下,myisam可以支持读写并发进行。
系统变量,concurrent_insert
0,不允许并发插入
1,如果表中间没有被删除的行,允许一个进程读表的同时,另一个进程从表尾插入记录。默认设置。
2,始终允许在表尾并发插入记录。
通过定期在系统空闲时段执行optimize table语句来整理空间碎片,收回因删除记录而产生的中间空洞。
myisam的锁调度
写进程先获得锁。锁等待队列中,写锁会查到读锁请求之前。这也正是myisam表不太适合于有大量更新和查询操作的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。
可以通过一些设置来调节myisam的调度行为。
low-priority-updates,使myisam引擎默认给予读请求优先权利
set low_priority_update=1,使该连接发出的更新请求优先级降低
指定insert、update、delete语句的low_priority属性,降低该语句的优先级
要么更新优先,要么查询优先
给max_write_lock_count 设置一个合适的值,当一个表的读锁达到这个值后,mysql就暂时将写请求的优先级降低
在可能的情况下可以通过使用中间表等措施对sql语句做一定的分解,使每一步查询都能在短时间内完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。
InnoDB锁问题
与muisam最大不同是,支持事务,采用行级锁。
获取行级锁争用情况
innodb_row_lock%
设置innodb_monitors
打开监视器
打开监视器后会自动将监视情况写入日志,所以为避免日志庞大要设置服务器关闭写日志文件
InnoDB的行锁模式及加锁方法
- 共享锁,允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁。
- 排它锁,允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
另外,为了允许行锁表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁,这两种都是表锁。 - 意向共享锁,事务给数据行加共享锁之前必须先取得该表的意向共享锁
- 意向排他锁,事务在各一个数据行加排它锁前必须先取得该表的意向排它锁。
意向锁是innodb自动加的,不需用户干预。对于更新语句,会自动加排它锁;select普通语句不会加锁。
用select...in share mode获得共享锁,主要确保没有人对这个记录进行更新操作,如果当前事务对该数据进行更新操作,会造成死锁?,需要select...for update 获得排它锁再更新。
同一个事务可以同时获得排他和共享锁??
死锁:
session1 加共享锁
session2 加共享锁
session1 更新
session1等待session2释放共享锁
session更新
session2等待session1释放共享锁
加排它锁:其他事务可正常查询,如果加共享锁会进行等待。
InnoDB行锁实现方式
通过给索引上的索引项加锁来实现的,只有通过索引条件检索数据,InnoDB才使用行锁,否则使用表锁。
锁冲突:因为是针对索引加锁,所以访问不同行的记录,如果是使用相同的索引键会出现锁冲突
如
id有索引name没有那么,
session1
where id = 1 and name = '1' for update
后
session2
where id = 1 and name = '4' for update
会等待锁
当表有多个索引时,不同事务可以使用不同的索引锁定不同的行,换句话说,有一个索引锁定一行后,其他索引不可锁定。
即便条件使用了索引字段,但是是否使用索引来检索数据是有mysql通过判断不同执行计划代价来决定的,如果认为全表扫描效率更高,比如对一些很小的表,它不会使用索引,这种情况下是使用表锁。
因此,在分析锁冲突时,别忘了检查sql的执行计划,已确认是否真正使用了索引
explain检查执行计划
类型转换??检索值的数据类型和索引字段不同,虽然mysql会进行数据类型的转换,但却不会使用索引,从而导致innodb使用表锁
如,条件中不是和varchar类型比较,会对title进行类型转换,执行全表扫描
加索引
mysql> alter table techo add index title(title(100));
varchar类型
mysql> explain select * from techo where title=56446545;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | techo | ALL | title | NULL | NULL | NULL | 37 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
非varchar类型
mysql> explain select * from techo where title=56446545;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | techo | ALL | title | NULL | NULL | NULL | 37 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
间隙锁(Next-Key锁)
当用范围条件检索数据,并请求共享或排它锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;
对于键值?在条件范围内但并不存在的记录,叫做间隙(GAP),InnoDB也会对这个间隙加锁,这种锁机制就是所谓的间隙锁。
使用间隙锁是为了防止幻读,另外一方面,是为了满足其恢复和复制的需要。
要尽量使用相等条件来访问更新数据,避免使用范围条件。但是使用相等条件给不存在的记录加锁,也会使用间隙锁。
恢复和复制的需要,对innodb锁机制的影响
通过BINLOG记录执行成功的insert、update、delete更新操作的sql语句,并由此实现mysql数据库的恢复和主从复制。
复制就是在slave mysql 不断做基于BINLOG的恢复。
恢复机制有以下特点:
- 恢复是sql语句级的,也就是重新执行BINLOG中的sql语句。而oracle是基于数据库文件块的。
- binlog是按照事务提交的先后顺序记录的,恢复也是按照这个顺序进行的。
所以mysql的恢复机制,要求事务串行化。
innodb无论什么隔离级别都要用间隙锁。
如果没有间隙锁,那假设表x中id=1的记录不存在,在事务a执行select * from x where id<100 但未提交时,又有事务b执行insert into x(id) value(1) 后提交了。那在binlog中的记录就是先b后a,这样a就幻读了。
另外,对于insert into target select * from source where... 和 create table new ...select...from source where... CTAS?这种语句,用户并没有对source做任何更新操作,但仍然会加共享锁。
其实使用多版本数据一致性读技术也可以实现相同的结果。但是这里mysql并没有使用它。
这是因为不加锁的话,如果在上述语句执行过程中,其他事务对source做了更新操作,就可能导致数据恢复的结果错误。如果复制的会,会导致主从数据库数据不一致。
innodb_locks_unsafe_for_binlog默认为off。
因此,INSERT...SELECT...和 CREATE TABLE...SELECT...语句,可能会阻止对源表的并发更新,造成对源表锁的等待。如果查询比较复杂的话,会造成严重的性能问题,我们在应用中应尽量避免使用。实际上,MySQL将这种SQL叫作不确定(non-deterministic)的SQL,不推荐使用。
可以采用来两种措施代替insert select和create table select语句
- 不推荐。将innodb_locks_unsafe_for_binlog设为on,强制使用MVCC。但可能无法用binlog正确地恢复或复制数据。
- 使用 select * from source into outfile ' ' terminated by ',' 和 load data infile ' ' into table x fields terminated by ','
innodb在不同隔离级别下的一致性读和锁的差异
innodb什么时候使用表锁
- 事务需要更新大部分或全部数据,表又大,可以考虑直接使用表锁来提高该事务的执行速度
- 事务涉及多个表,可能引起死锁造成回滚
使用lock tables表锁需要注意:
- 仅当autocommit=0、innodb_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;
- 在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。
关于死锁
myisam是deadlock free 无死锁的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。
innodb因为是逐步获得锁的,因此可能发生死锁。
发生死锁后,innodb一般都能自动检测到,并使一个事务释放锁并回滚,让另一个事务得以继续进行。
锁等待超时参数innodb_lock_wait_timout 可解决表锁或外部锁死锁情况。并且在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖垮数据库。通过设置超时,可以避免这种情况发生。
避免死锁的常用方法。
- 在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。在下面的例子中,由于两个session访问两个表的顺序不同,发生死锁的机会就非常高!但如果以相同的顺序来访问,死锁就可以避免。
- 在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
- 如果要更新记录,应该直接申请足够级别的锁,即排它锁,而不应先申请共享锁,更新时再申请排它锁。因为期间可能其他事务获得了共享锁。
如果出现死锁,可以用show engine innodb status 命令来确定最后一个死锁产生的原因。
返回结果中有死锁相关事务的详细信息。