MySQL锁详解

MySQL锁分类

 

锁是计算机协调多个进程或线程并发访问某一资源的机制。

读锁和写锁:

读锁

给student加读锁

lock table student read;

解锁unlock tables;

当本线程锁表

其他线程进行操作时

可以查看,当修改时会进入阻塞状态

解锁之后

修改成功:

一共等了2 min 54.03 sec

需要一提的是,如果给student加锁后,就不能查找其他表,会抛出异常

写锁

其他线程进行操作时

不管是读还是写都会进入阻塞状态中

当释放写锁以后

mysql> unlock tables;
Query OK, 0 rows affected (0.00 sec)

mysql>
 

 

案例结论
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
MySQL的表级锁有两种模式: 
表共享读锁(Table Read Lock)
表独占写锁(Table Write Lock)
结合上表,所以对MyISAM表进行操作,会有以下情况:
1、对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,
才会执行其它进程的写操作。
2、对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操
作。
 

简而言之,就是读锁会阻塞写,但是不会堵塞读。而写锁则会把读和写都堵塞。

 

事务:

后面行锁会涉及到现在提前说一下


事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
一致性(Consistent) :在事务开始和完成时,数据都必须保持一 致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。


并发事务处理带来的问题

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题,导致最后的更新覆盖了由其他事务所做的更新。
        例如,两个程序员修改同一java文件。每程序员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖前一一个程序员所做的更改。
如果在一一个程序员完成并提交事务之前,另一个程序员不能访问同一文件,则可避免此问题。

脏读

      一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另-一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一 步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。
      句话:事务A读取到了事务B已修改但尚未提交的的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取.的数据无效,不符合一致性要求。

 

脏读:就是读取到未提交的事务(已修改为提交),然后发生了事务回滚

事务1,修改number=100
事务2,读取到number=100
事务1,异常回滚number变为原来的50
事务2,读取的就是脏数据

 

不可重复读

       一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

不可重复读:读取了提交的新事务,指更新操作(update)

事务2,读取到number=100
事务1,修改number=50
事务2,再次读取时number=50

不符合隔离性  事务A读取到了事务B已经提交的修改数据

幻读

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读   例如:读取了提交的新数据,如读A后插入了元素B
一句话:事务A读取到了事务B体提交的新增数据,不符合隔离性。
多说一句:幻读和脏读有点类似,
脏读是事务B里面修改了数据,
幻读是事务B里面新增了数据。

事务的隔离级别

脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一 致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

mysql默认的是可重复读

创建表为行锁做基础

mysql> create table test_innodb_lock(a int(11),b varchar(16))engine=innodb;
Query OK, 0 rows affected (0.24 sec)

mysql> insert into test_innodb_lock values(1,'b2');
Query OK, 1 row affected (0.31 sec)

mysql> insert into test_innodb_lock values(2,'b1');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_innodb_lock values(3,'4000');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_innodb_lock values(4,'5000');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_innodb_lock values(5,'5000');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_innodb_lock values(6,'6000');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_innodb_lock values(7,'7000');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_innodb_lock values(8,'8000');
Query OK, 1 row affected (0.01 sec)

mysql> create index test_innodb_a_ind on test_innodb_lock(a);
Query OK, 0 rows affected (0.37 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> create index test_innodb_b_ind on test_innodb_lock(b);
Query OK, 0 rows affected (0.14 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> select * from test_innodb_lock;
+------+------+
| a    | b    |
+------+------+
|    1 | b2   |
|    2 | b1   |
|    3 | 4000 |
|    4 | 5000 |
|    5 | 5000 |
|    6 | 6000 |
|    7 | 7000 |
|    8 | 8000 |
+------+------+
8 rows in set (0.00 sec)

 

行锁

InnoDB(默认开启行锁的,默认还是自动提交,演示的时候关闭自动提交set autocommit=0;)

偏向InnoDB存储引擎, 开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。


InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION) ;二是采用了行级锁

线程一:

线程二

查找是发现a=3对应的b等于4000

因为没有提交commit;

下面commit之后就可以找到对应的数据了

左边线程一, 右边线程二(必须commit,否则读取不到,因为Mysql默认的事务隔离级别为可重复读)

当线程一对a等于3修改时,线程二再次修改a等于3时会进入阻塞状态

当线程一读a等于3修改时,线程二还可以对a==2修改;

索引失效行锁变成表锁

刚开始我们创建的两个索引test_innodb_a_ind和test_innodb_b_ind

       create table test_innodb_lock(a int(11),b varchar(16))engine=innodb;

如果我们使用b查询时去掉双引号时,索引就会失效(因为只有索引上面才能创建行锁)

例如:

回归正题:

因为b原始类型为b(varchar),误把b当成了int类型查询(忘记加双引号),test_innodb_b_ind索引就会失效,所以当线程二去修改其它行时也会被阻塞

间隙锁

虽然数据中没有a==2的数据,但是属于间隙锁里面的,所以线程二添加a==2也会被阻塞

 

什么是间隙锁


        当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,
        InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key锁) 。


危害


        因为Query执行过程中通过过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后, 即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定键值范围内的任何数据。在某些场景下这可能会对性能造成很大的危害
 

如何锁定具体的某一行数据

(如a==3)

前提是需要索引才行,没有索引就是表锁,线程二访问也是会阻塞 

下图证明加锁成功,

行锁总结:

Innodb存储引擎由于实现了行级锁定,虽然在锁定机制的实现方面所带来的性能损耗可能比表级锁定会要更高-一些,但是在整体并发处理能力方面要远远优于MyISAM的表级锁定的。当系统并发量较高的时候,Innodb的整体性能和MyISAM相比就会有比较明显的优势了。|
但是,Innodb的行 级锁定同样也有其脆弱的一面,当我们使用不当的时候,可能会让Innodb的整体性能表现不仅不能比MyISAM高,甚至可能会更差。
 

如何分析行锁定


通过检查InnoDB_ row_ lock状态变量来分析系统上的行锁的争夺情况
mysq|l> show status like 'innodb_row_lock%’
 

对各个状态量的说明如下:
Innodb_ row_lock__current_waits: 当前正在等待锁定的数量;
Innodb_ row_ lock_time: 从系统启动到现在锁定总时间长度;
Innodb_row_ lock_time_avg: 每次等待所花平均时间;
Innodb_row_lock_timne_max:从系统启动到现在等待最常的一次所花的时间;
Innodb_row_lock_waits: 系统启动后到现在总共等待的次数;
对于这5个状态变量,比较重要的主要是
Innodb_row_lock_time_avg (等待平均时长),
Innodb_row_lock_waits ( 等待总次数)
Innodb_row_lock_time (等待总时长)这三项。

尤其是当等待次数很高,而且每次等待时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手指定优化计划。


优化建议

 

尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁。
合理设计索引,尽量缩小锁的范围
尽可能较少检索条件,避免间隙锁
尽量控制事务大小,减少锁定资源量和时间长度
尽可能低级别事务隔离
 

 

 

锁的类型有哪些


基于锁的属性分类:共享锁、排他锁。


基于锁的粒度分类:行级锁(INNODB)、 表级锁(INNODB、MYISAM)、 页级锁(BDB引擎 )、记录锁、间隙锁、临键锁。


基于锁的状态分类:意向共享锁、意向排它锁。


   ●   共享锁(Share Lock)


        共享锁又称读锁,简称S锁;当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。


   ●    排他锁(eXclusive Lock)


          排他锁又称写锁,简称X锁;当一个事 务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁。排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取。避免了出现脏数据和脏读的问题。


   ●   表锁


         表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问;
        特点:粒度大, 加锁简单,容易冲突;


  ●   行锁 


       行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的记录不能访问,其他的记录可正常访问; 
        特点:粒度小,加锁比表锁麻烦,不容易冲究,相比表锁支持的并发要高;


  ●   记录锁(Record Lock)


       记录锁也属于行锁中的一种, 只不过记录锁的范围只是表中的某一条记录,记录锁是说小务在加锁后锁住的只是表的某一条记录。
       精准条件命中,并且命中的条件字段是唯一索引
       加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。
 

  ●   页锁


     页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。 表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般


  ●   间隙锁(Gap Lock)


         属于行锁中的一种, 间隙锁是在小务加锁后其锁住的是表记录的某一个区间,当表的相邻ID之间出现空陈则会形成。个区间,遵循左开右闭原则。
         范围查询并且查询未命中记录,查询条件必须命中索引、间隙锁只会出现在REPEATABLE READ (重复读)的事务级别中。
         触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下图的问题,在同一个事务里,A事务的两次查询出的结果会不一样。
         比如表里面的数据ID为1,4,5,7,10 , 那么会形成以下几个间隙区间,-n-1区间,1-4区间,7-10区间,10-n区间(-n代表负无穷大, n代表正无穷大)


  ●  临建锁(Next-Key Lock)


       也属于行锁的一种,并且它是INNODB的行锁默认算法,总结来说它就是记录锁和间隙锁的组合,临键锁会把查询出来的记录锁住,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住
        触发条件:范围查询并命中,查询命中了索引。  
         结合记录锁和间隙锁的特性,临键锁避免了在范围查询时出现脏读、重复读、幻读问题。加了临键锁之后,在范围区间内数据不允许被修改和插入。
 

意向锁

如果当事务A加锁成功之后就设置一个状态告诉后面的人, 已经有人对表里的行加了一个排他锁了,你们不能对整个表加共享锁或排它锁了,那么后面需要对整个表加锁的人只需要获取这个状态就知道自己是不是可以对表加锁,避免了对整个索引|树的每个节点扫描是否加锁,而这个状态就是意向锁。
   ●     意向共享锁
         当一个事务试图对整个表进行加共享锁之前,首先需要获得这个表的意向共字锁。
   ●     意向排他锁
         当一个事务试图对整个表进行加排它锁之前,首先需要获得这个表的意向排它锁。
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值