MySQL中的锁

一、锁概述

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的 计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

1、锁分类:

页级锁、表级锁、行级锁

2、页级锁:

它是锁住的一个页面,在 InnoDB 中一个页面为16KB,它的开销介于表级锁和行级锁中间,也可能会出现死锁,锁定粒度也介于表级锁和行级锁中间,并发度也介于表级锁和行级锁中间。

BDB 引擎使用的是页级锁,也支持表级锁。BDB 引擎基本已经成为历史。
3、表锁

它直接锁住的是一个表,开销小,加锁快,不会出现死锁的情况,锁定粒度大,发生锁冲突的概率更高,并发度最低。

MyISAM 和 Memory 存储引擎使用的是表级锁
4、行锁

它直接锁住的是一条记录,开销大,加锁慢,发生锁冲突的概率较低,并发度很高。InnoDB 存储引擎既支持行级锁,也支持表级锁,默认情况下使用行级锁。

5、对比

仅仅从锁的角度来说,表级锁更加适合于以查询为主的应用,只有少量按照索引条件更新数据的应用。行级锁更适合大量按照索引条件并发更新少量不同的数据,同时还有并发查询的应用

6、悲观锁、乐观锁:

这是一个抽象的描述,对于是否使用锁

 悲观锁:一定要加锁才能实现。适合写
 乐观锁(MVCC):不加锁也能实现想要的效果。适合读。

乐观锁的实现:

MySql最经常使用的乐观锁是进行版本控制,也就是在数据库表中增加一列,记为version,当我们将数据读出时,将版本号一并读出,当数据进行更新时,会对这个版本号进行加1,当我们提交数据时,会判断数据库表中当前的version列值和当时读出的version是否相同,若相同说明没有进行更新的操作,不然,则取消这次的操作。mysql中一条 增删改 语句其实就是一个内置的事务。事务的本质是加行级锁!

Innod行级锁
1、排他锁(写锁、X锁)

排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
任何锁一起使用都会产生冲突。不可以读,不可以写。凡是执行insert、update、delete的时候都会加一个排他锁。
原理:一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁(排他锁或者共享锁),即一个事务在读取一个数据行的时候,其他事务不能对该数据行进行增删改,不加锁的查是可以的,加锁的查是不可以的

排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他锁。
mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给
涉及到的数据加上排他锁,select语句默认不会加任何锁类型,所以加过排他锁的数
据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁
的方式查询数据,但可以直接通过select ...from...查询数据,因为普通查询没有任何
锁机制。
2、共享锁(读锁、简称S锁)

共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
原理:一个事务获取了一个数据行的共享锁,其他事务能获得该行对应的共享锁,但不能获得排他锁,即一个事务在读取一个数据行的时候,其他事务也可以读,但不能对该数据行进行增删改(因为增删改都是自动加排它锁)。
共享锁和任何锁一起使用都不冲突。只能读,不能写

3、设置共享锁和排他锁

设置共享锁:SELECT … LOCK IN SHARE MODE; 共享锁都是行锁设置排他锁:SELECT … FOR UPDATE; 排它锁可以使行锁也可以是表锁

1、对于select 语句,innodb不会加任何锁,也就是可以多个并发去进行select的操作,不会有任何的锁冲突,因为根本没有锁。
2、对于insert,update,delete操作,innodb会自动给涉及到的数据加排他锁,只有查询select需要我们手动设置
4、意向共享锁和意向排他锁

1、意向共享锁,简称IS,其作用在于:通知数据库接下来需要施加什么锁并对表加锁。如果需要对记录A加共享锁,那么此时innodb会先找到这张表,对该表加意向共享锁之后,再对记录A添加共享锁。也就是说事务在给一个数据行加共享锁前必须先取得该表的IS锁。
2、意向排他锁,简称IX,其作用在于:通知数据库接下来需要施加什么锁并对表加锁。如果需要对记录A加排他锁,那么此时innodb会先找到这张表,对该表加意向排他锁之后,再对记录A添加排他锁。也就是说事务在给一个数据行加排他锁前必须先取得该表的IX锁。
3、意向共享锁和意向排它锁是数据库主动加的,不需要我们手动处理

5、普通锁和意向锁的区别

1、共享锁和排他锁,系统在特定的条件下会自动添加共享锁或者排他锁,也可以手动添加共享锁或者排他锁。
2、意向共享锁和意向排他锁都是系统自动添加和自动释放的,整个过程无需人工干预。
3、共享锁和排它锁,可能锁定的是表,也可能锁定的是行
例如

SELECT count(*) as total FROM test WHERE username = "mraz" FOR UPDATE

当username是主键时,锁定的是行锁,
当username不是主键时,是表锁

4、InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才会使用行级锁,否则,InnoDB将使用表锁!在实际应用中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。

5、意向共享锁和意向排他锁锁定的是表。

意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会任何锁;事务可以通过以下语句显示给记录集加共享锁或排锁。

用SELECT … IN SHARE MODE获得共享锁,主要用在需要数据依存关系时确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT … FOR UPDATE方式获取排他锁。

6、获取InonoD行锁争用情况

1、死锁:因为锁相互冲突,所以产生死锁。事务的底层就是行级锁。用到事务的时候就用到锁
2、可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:

mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
+-------------------------------+-------+
5 rows in set (0.00 sec)

如果发现争用比较严重,如Innodb_row_lock_waits和Innodb_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。

补充
innodb 行锁与表锁转变

1、InnoDB 行级锁是通过给索引上的索引项加锁来实现的,InnoDB行级锁只有通过索引条件检索数据,才使用行级锁;否则,InnoDB使用表锁
2、在不通过索引(主键)条件查询的时候,InnoDB是表锁而不是行锁(共享锁不会有这个情况,只有排他锁存在)。
3、如果查询的条件没有带索引,那么行锁则会转为表锁,即使表中字段有主键;所以在查询的时候建议使用索引字段查询
4、自己总结:innodb里面 当进行加速的数据不确定时,所加的锁一定是表锁,不是行锁。

innodb间隙锁

可以理解为是对于一定范围内的数据进行锁定,如果说这个区间没有这条数据的话也是会锁住的;主要是解决幻读的问题,如果没有添加间隙锁,如果其他事物中添加id在1到100之间的某条记录,此时会发生幻读;另一方面,视为了满足其恢复和赋值的需求。

MySQL官网间隙锁的使用 https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html#innodb-gap-locks
MySQL官网间隙锁属性解释 https://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_locks_unsafe_for_binlog

默认情况下,innodb_locks_unsafe_for_binlog是0(禁用),这意味着启用了间隙锁定:InnoDB使用下一个键锁进行搜索和索引扫描。若要启用该变量,请将其设置为1。这将导致禁用间隙锁定:InnoDB只使用索引记录锁进行搜索和索引扫描。

MySQL对于死锁的处理方式

1、产生死锁的原因:
两个事务都持有对方需要的锁,并且在等待对方释放,并且双方都不会释放自己的锁。
在这里插入图片描述
在这里插入图片描述
2、MySQL有两种死锁处理方式:

  • 等待,直到超时(innodb_lock_wait_timeout=50s)。
  • 发起死锁检测,主动回滚一条事务,让其他事务继续执行(innodb_deadlock_detect=on)。
    由于性能原因,一般都是使用死锁检测来进行处理死锁。

3、死锁检测

  • 原理
    死锁检测的原理是构建一个以事务为顶点、锁为边的有向图,判断有向图是否存在环,存在即有死锁。

  • 回滚
    检测到死锁之后,选择插入更新或者删除的行数最少的事务回滚,基于 INFORMATION_SCHEMA.INNODB_TRX 表中的 trx_weight 字段来判断。

4、使用锁的注意事项

收集死锁信息
   利用命令 SHOW ENGINE INNODB STATUS查看死锁原因。
   调试阶段开启 innodb_print_all_deadlocks,收集所有死锁日志。
减少死锁:
   使用事务,不使用 lock tables 。
   保证没有长事务。
   操作完之后立即提交事务,特别是在交互式命令行中。
   如果在用 (SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE),尝试降低隔离级别。
   修改多个表或者多个行的时候,将修改的顺序保持一致。
   创建索引,可以使创建的锁更少。
   最好不要用 (SELECT ... FOR UPDATE or SELECT ... LOCK IN SHARE MODE)。
   如果上述都无法解决问题,那么尝试使用 lock tables t1, t2, t3 锁多张表

5、使用事务注意事项:
innodb存储引擎由于实现了行级锁,颗粒更小,实现更复杂。但是innodb行锁在并发性能上远远要高于表锁页锁。在使用方面可以尽量做到以下几点;

a、控制事务大小,减少锁定的资源量和锁定时间长度。
b、让所有的数据检索都通过索引来完成,从而避免因为无法通过索引加锁而升级为表锁。
c、减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。
d、在业务条件允许下,尽量使用较低隔离级别的事务隔离。减少隔离级别带来的附加成本。
e、合理使用索引,让innodb在索引上面加锁的时候更加准确。
f、在应用中尽可能做到访问的顺序执行
g、如果容易死锁,就可以考虑使用表锁来减少死锁的概率

6、自己的总结

1、mysql在锁产生的时候会优先回滚事务小的。事务大的会优先执行。这里的大小指的是影响的数据量。就是遇到死锁的时候,谁的事务大谁就有话语权,就是优先执行。
2、使用事务的时候,尽量不要让事务太大。控制事务大小。
3、尽量使用索引作为查询条件查询数据,如果不使用索引会把行锁变成表锁,容易产生死锁。
4、尽量不使用间隙锁,使用间隙锁时尽量减小范围
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值