MySql(三) MySql中的锁机制

前面两篇博客中简单的聊了下mysql中的索引,今天聊聊mysql(InnoDB引擎)中的锁以及事务的实现

MySql(一) 浅析MySql索引
html

MySQL(二) MySql经常使用优化
mysql


讲到锁你们应该都不陌生。像是Java中常见的采用CAS算法实现的乐观锁,典型的例子就是原子类,经过CAS自旋实现原子操做的更新,悲观锁一般都是 SynchronizedLock实现。

乐观锁与悲观锁

  • 乐观锁:每次读数据的时候都认为其余人不会修改,因此不会上锁,而是在更新的时候去判断在此期间有没有其余人更新了数据,可使用版本号机制。在数据库中能够经过为数据表增长一个版本号字段实现。读取数据时将版本号一同读出,数据每次更新时对版本号加一。当咱们更新的时候,判断数据库表对应记录的当前版本号与第一次取出来的版本号值进行比对,若是值相等,则予以更新,不然认为是过时数据。乐观锁适用于多读的应用类型,能够提升吞吐量。
  • 悲观锁:每次读数据的时候都认为别人会修改,因此每次在读数据的时候都会上锁,这样别人想读这个数据时就会被阻塞。MySQL中就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在操做以前先上锁。


共享锁与排他锁

  • 共享锁:共享锁又叫作读锁或S锁,加上共享锁后在事务结束以前其余事务只能再加共享锁、只能对其进行读操做不能写操做,除此以外其余任何类型的锁都不能再加了。

# 加上lock in share mode
SELECT description FROM book_book lock in share mode;复制代码

  • 排他锁:排他锁又叫写锁或X锁,某个事务对数据加上排他锁后,只能这个事务对其进行读写,在此事务结束以前,其余事务不能对其加任何锁,能够读取,不能进行写操做,需等待其释放。

# 加上for update
SELECT description FROM book_book for update; 
复制代码


行锁与表锁

行锁与表锁区别在于锁的粒度,在Innodb引擎中既支持行锁也支持表锁(MyISAM引擎只支持表锁),只有经过索引条件检索数据InnoDB才使用行级锁,不然,InnoDB将使用表锁。
  • 表锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突几率高,并发度最低
  • 行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的几率低,并发度高

这里有个比较疑惑的地方,为何表锁不会出现死锁?在MyISAM中因为没有事务,一条SQL执行完锁就释放了,不会循环等待,因此只会出现阻塞而不会发生死锁。可是在InnoDB中有事务就比较疑惑了,但愿有了解的小伙伴指点指点@-@算法

下面举两个例子说明上面几种锁:

# 事务1
BEGIN;
SELECT description FROM book_book where name = 'JAVA编程思想' lock in share mode;

# 事务2
BEGIN;
UPDATE book_book SET name = 'new book' WHERE name = 'new';

# 查看事务状态
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

trx_id  trx_state       trx_started           trx_tables_locked    trx_rows_locked
39452	LOCK WAIT	2018-09-08 19:01:39	    1	                1	
282907511143936	RUNNING	2018-09-08 18:58:47	    1	                38	
复制代码

事务1给book表加上了共享锁,事务2尝试修改book表发生了阻塞,查看事务状态能够知道事务一因为没有走索引使用了表锁。
sql

# 事务1
BEGIN;
SELECT description FROM book_book WHERE id = 2 lock in share mode;

# 事务2
BEGIN;
UPDATE book_book SET name = 'new book' WHERE id = 1; 

# 查看事务状态
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

trx_id          trx_state   trx_started     trx_tables_locked    trx_rows_locked
39454	        RUNNING	2018-09-08 19:10:44	1	                1	
282907511143936	RUNNING	2018-09-08 19:10:35	1	                1	
复制代码

事务1给book表加上了共享锁,事务2尝试修改book表并无发生阻塞。这是因为事务一和事务二都走了索引,因此使用的是行锁,并不会发生阻塞。
数据库


意向锁(InnoDB特有)

意向锁的意义在于方便检测表锁和行锁之间的冲突
  • 意向锁:意向锁是一种表级锁,表明要对某行记录进行操做。分为意向共享锁(IS)和意向排他锁(IX)。
  • 行锁和表锁之间的冲突:事务A给表中的某一行加了共享锁,让这一行只能读不能写。以后事务B申请整个表的排他锁。若是事务B申请成功,那么它就能修改表中的任意一行,这与A持有的行锁是冲突的。InnoDB引入了意向锁来判断它们之间的冲突。
    • 没有意向锁的状况:一、判断表是否已被其余事务用表锁锁表。二、判断表中的每一行是否已被行锁锁住,这样要遍历整个表,效率很低。
    • 意向锁存在的状况:一、判断表是否已被其余事务用表锁锁表。二、判断表上是否有意向锁
  • 意向锁存在时申请锁:申请意向锁的动做是数据库完成的,上述例子中事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,当事务B申请表的排他锁时检测到存在乎向锁则会阻塞。
  • 意向锁会不会存在冲突: 意向锁之间不会冲突, 由于意向锁只是表明要对某行记录进行操做。


各类锁之间的共存状况

IX     IS       X      S
IX    兼容    兼容    冲突    冲突
IS    兼容    兼容    冲突    兼容
X     冲突    冲突    冲突    冲突
S     冲突    兼容    冲突    兼容
复制代码


死锁

  • 概念:两个或两个以上的事务在执行过程当中,因争夺资源而形成的一种互相等待的现象。
  • 存在条件:一、 互斥条件:一个资源每次只能被一个事务使用。二、 请求与保持条件:一个事务因请求资源而阻塞时,对已得到的资源保持不放。三、不剥夺条件:已得到的资源,在末使用完以前不能强行剥夺。四、循环等待条件:造成一种头尾相接的循环等待关系
  • 解除正在死锁的状态:撤销其中一个事务


MVCC(多版本并发控制)

MVCC使得InnoDB更好的实现事务隔离级别中的REPEATABLE READ
  •  它使得InnoDB再也不单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只须要很小的开销,就能够实现非锁定读,从而大大提升数据库系统的并发性能。
  • 实现:InnoDB实现MVCC的方法是它为每一行存储三个额外的隐藏字段
    • 1.DB_TRX_ID:一个6byte的标识,每处理一个事务,其值自动+1 ,能够经过语句“show engine innodb status”来查找
    • 2.DB_ROLL_PTR: 大小是7byte,指向写到rollback segment(回滚段)的一条undo log记录 
    • 3.DB_ROW_ID: 大小是6byte,该值随新行插入单调增长。
  • SELECT:返回的行数据须要知足的条件:  一、数据行的建立版本号必须小于等于事务的版本二、行的删除版本号(行中的特殊位被设置为将其标记为已删除)必定是未定义的或者大于当前事务的版本号,肯定了当前事务开始以前行没有被删除。
  • INSERT:InnoDB为每一个新增行记录当前系统版本号做为建立版本号。
  • DELETE:InnoDB为每一个删除行的记录当前系统版本号做为行的删除版本号。
  • UPDATE:InnoDB复制了一条数据。这条数据的版本号使用了系统版本号。它也把系统版本号做为老数据的删除号。
  • 说明:这里的读是不加锁的select等,MVCC实现可重复读使用的是读取undo中的已经提交的数据,是非阻塞的。insert操做时"建立时间"=DB_ROW_ID,这时"删除时间"是未定义的;update时,复制新增行的"建立时间"=DB_ROW_ID,删除时间未定义,旧数据行"建立时间"不变,删除时间=该事务的DB_ROW_ID; delete操做,相应数据行的"建立时间"不变,删除时间=该事务的DB_ROW_ID;


间隙锁(Next-Key锁)

间隙锁使得InnoDB解决幻读问题,加上MVCC使得InnoDB的RR隔离级别实现了串行化级别的效果,而且保留了比较好的并发性能。

定义:当咱们用范围条件检索数据时请求共享或排他锁时,InnoDB会给符合条件的已有数据的索引加锁;对于键值在条件范围内但并不存在的记录,叫作间隙(GAP),InnoDB也会对这个"间隙"加锁,这种锁机制就是间隙锁。
编程

例如:book表中存在bookId 1-80,90-99的记录。SELECT * FROM book WHERE bookId < 100 FOR UPDATE。InnoDB不只会对bookId值为1-80,90-99的记录加锁,也会对bookId在81-89之间(这些记录并不存在)的间隙加锁。这样就能避免事务隔离级别可重复读下的幻读。bash






有问题的同窗能够指出相互探讨,如需转载请注明出处。
参考文献:
https://dev.mysql.com/doc/refman/5.7/en/innodb-multi-versioning.htmlhttps://www.cnblogs.com/chenpingzhao/p/5065316.html
相关文章
相关标签/搜索