从新学习Mysql数据库7:详解MyIsam与InnoDB引擎的锁实现

说到锁机制以前,先来看看Mysql的存储引擎,毕竟不一样的引擎的锁机制也随着不一样。mysql

三类常见引擎:

MyIsam :不支持事务,不支持外键,因此访问速度快。锁机制是表锁,支持全文索引sql

InnoDB :支持事务、支持外键,因此对比MyISAM,InnoDB的处理效率差一些,并要占更多的磁盘空间保留数据和索引。锁机制是行锁,不支持全文索引数据库

Memory:数据是存放在内存中的,默认哈希索引,很是适合存储临时数据,服务器关闭后,数据会丢失掉。segmentfault

如何选择存储引擎:

MyISAM:应用是以读操做和插入操做为主,只有不多的更新和删除操做,而且对事务的完整性、并发性要求不是很高。安全

InnoDB:用于事务处理应用程序,支持外键,若是应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性。更新删除等频繁(InnoDB能够有效的下降因为删除和更新致使的锁定),对于数据准确性要求比较高的,此引擎适合。服务器

Memory:一般用于更新不太频繁的小表,用以快速获得访问结果。多线程

Mysql中的锁

若是熟悉多线程,那么对锁确定是有概念的,锁是计算机协调多个进程或线程对某一资源并发访问的机制。并发

Mysql中的锁分为表锁和行锁:spa

顾名思义,表锁就是锁住一张表,而行锁就是锁住一行。线程

表锁的特色:开销小,不会产生死锁,发生锁冲突的几率高,而且并发度低。

行锁的特色:开销大,会产生死锁,发生锁冲突的几率低,并发度高。

所以MyISAM和Memory引擎采用的是表锁,而InnoDB存储引擎采用的是行锁。

MyISAM的锁机制:

分为共享读锁和独占写锁。

读锁是:当某一进程对某张表进行读操做时(select),其余线程也能够读,可是不能写。简单的理解就是,我读的时候你不能写。

写锁是:当某一进程对某种表某张表的写时(insert,update,,delete),其余线程不能写也不能读。能够理解为,我写的时候,你不能读,也不能写。

所以MyISAM的读操做和写操做,以及写操做之间是串行的!MyISAM在执行读写操做的时候会自动给表加相应的锁(也就是说不用显示的使用lock table命令),MyISAM老是一次得到SQL语句所须要的所有锁,这也是MyISAM不会出现死锁的缘由。

下面分别举关于写锁和读锁的例子:

写锁:

事务1  事务2
取得first_test表的写锁:mysql> lock table first_test write;Query OK, 0 rows affected (0.00 sec)
当前事务对查询、更新和插入操做均可以执行mysql> select * from first_test ;+----+------+  id    age   +----+------+   1      10     2      11     3      12     4      13   +----+------+4 rows in set (0.00 sec)mysql> insert into first_test(age) values(14);Query OK, 1 row affected (0.11 sec) 其余事务对锁定表的查询被阻塞,须要等到锁被释放,才能够执行mysql> select * from first_test;等待......
mysql> unlock table;Query OK, 0 rows affected (0.00 sec) 等待
  mysql> select * from first_test;+----+------+  id    age   +----+------+   1      10     2      11    3      12    4      13    5      14  +----+------+5 rows in set (9 min 45.02 sec)

读锁例子以下:

事务1 事务2
得到表first_read的锁定mysql> lock table first_test read;Query OK, 0 rows affected (0.00 sec)  
当前事务能够查询该表记录:mysql> select * from first_test;+----+------+  id    age +----+------+   1      10    2      11    3      12    4      13    5      14  +----+------+5 rows in set (0.00 sec) 其余事务也能够查到该表信息mysql> select * from first_test;+----+------+  id    age   +----+------+   1      10    2      11    3      12    4      13    5      14  +----+------+5 rows in set (0.00 sec)
可是当前事务不能查询没有锁定的表:mysql> select * from goods;ERROR 1100 (HY000): Table 'goods' was not locked with LOCK TABLES 其余事务能够查询或更新未锁定的表:mysql> select * from goods;+----+------------+------+  id    name      num +----+------------+------+   1    firstGoods      11    3   ThirdGoods     11    4   fourth                11  +----+------------+------+10 rows in set (0.00 sec)
并且插入更新锁定的表都会报错:mysql> insert into first_test(age) values(15);ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updatedmysql> update first_test set age=100 where id =1;ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updated 当更新被锁定的表时会等待:mysql> update first_test set age=100 where id =1;等待......
mysql> unlock table;Query OK, 0 rows affected (0.00 sec) mysql> update first_test set age=100 where id =1;Query OK, 1 row affected (38.82 sec)Rows matched: 1  Changed: 1  Warnings: 0

并发插入

刚说到Mysql在插入和修改的时候都是串行的,可是MyISAM也支持查询和插入的并发操做。

MyISAM中有一个系统变量concurrent_insert(默认为1),用以控制并发插入(用户在表尾插入数据)行为。

当concurrent_insert为0时,不容许并发插入。

当concurrent_insert为1时,若是表中没有空洞(中间没有被删除的行),MyISAM容许一个进程在读表的同时,另外一个进程从表尾插入记录。

当concurrent_insert为2时,不管MyISAM表中有没有空洞,均可以在末尾插入记录

事务1    事务2
mysql> lock table first_test read local;Query OK, 0 rows affected (0.00 sec)--加入local选项是说明,在表知足并发插入的前提下,容许在末尾插入数据  
当前进程不能进行插入和更新操做mysql> insert into first_test(age) values(15);ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updatedmysql> update first_test set age=200 where id =1;ERROR 1099 (HY000): Table 'first_test' was locked with a READ lock and can't be updated 其余进程能够进行插入,可是更新会等待:mysql> insert into first_test(age) values(15);Query OK, 1 row affected (0.00 sec)mysql> update first_test set age=200 where id =2;等待.....
当前进程不能不能访问其余进程插入的数据mysql> select * from first_test;+----+------+  id   age   +----+------+   1    100    2     11    3     12    4     13    5     14    6     14  +----+------+6 rows in set (0.00 sec)  
释放锁之后皆大欢喜mysql> unlock table;Query OK, 0 rows affected (0.00 sec) 等待
插入的和更新的都出来的:mysql> select * from first_test;+----+------+  id   age   +----+------+   1    100    2    200    3     12    4     13    5     14    6     14    7     15  +----+------+7 rows in set (0.00 sec) mysql> update first_test set age=200 where id =2;Query OK, 1 row affected (1 min 39.75 sec)Rows matched: 1  Changed: 1  Warnings: 0

须要注意的:

并发插入是解决对同一表中的查询和插入的锁争用。

若是对有空洞的表进行并发插入会产生碎片,因此在空闲时能够利用optimize table命令回收因删除记录产生的空洞。

锁调度

在MyISAM中当一个进程请求某张表的读锁,而另外一个进程同时也请求写锁,Mysql会先让后者得到写锁。即便读请求比写请求先到达锁等待队列,写锁也会插入到读锁以前。

由于Mysql老是认为写请求通常比读请求重要,这也就是MyISAM不太适合有大量的读写操做的应用的缘由,由于大量的写请求会让查询操做很难获取到读锁,有可能永远阻塞。

处理办法:

一、指定Insert、update、delete语句的low_priority属性,下降其优先级。

二、指定启动参数low-priority-updates,使得MyISAM默认给读请求优先的权利。

三、执行命令set low_priority_updates=1,使该链接发出的请求下降。

四、指定max_write_lock_count设置一个合适的值,当写锁达到这个值后,暂时下降写请求的优先级,让读请求获取锁。

可是上面的处理办法形成的缘由就是当遇到复杂的查询语句时,写请求可能很难获取到锁,这是一个很纠结的问题,因此咱们通常避免使用复杂的查询语句,若是如法避免,则能够再数据库空闲阶段(深夜)执行。

咱们知道mysql在之前,存储引擎默认是MyISAM,可是随着对事务和并发的要求愈来愈高,便引入了InnoDB引擎,它具备支持事务安全等一系列特性。

InnoDB锁模式

InnoDB实现了两种类型的行锁。

共享锁(S):容许一个事务去读一行,阻止其余事务得到相同的数据集的排他锁。

排他锁(X):容许得到排他锁的事务更新数据,可是组织其余事务得到相同数据集的共享锁和排他锁。

能够这么理解:

共享锁就是我读的时候,你能够读,可是不能写。排他锁就是我写的时候,你不能读也不能写。其实就是MyISAM的读锁和写锁,可是针对的对象不一样了而已。

除此以外InnoDB还有两个表锁:

意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁

意向排他锁(IX):相似上面,表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。

InnoDB行锁模式兼容列表:

注意:

当一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之若是请求不兼容,则该事务就等待锁释放。

意向锁是InnoDB自动加的,不须要用户干预。

对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于通常的Select语句,InnoDB不会加任何锁,事务能够经过如下语句给显示加共享锁或排他锁。

共享锁:select * from table_name where .....lock in share mode

排他锁:select * from table_name where .....for update

加入共享锁的例子:

利用select ....for update加入排他锁

锁的实现方式:

InnoDB行锁是经过给索引项加锁实现的,若是没有索引,InnoDB会经过隐藏的聚簇索引来对记录加锁。

也就是说:若是不经过索引条件检索数据,那么InnoDB将对表中全部数据加锁,实际效果跟表锁同样。

行锁分为三种情形:

Record lock :对索引项加锁,即锁定一条记录。

Gap lock:对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包含记录自己

Next-key Lock:锁定一个范围的记录并包含记录自己(上面二者的结合)。

注意:InnoDB默认级别是repeatable-read级别,因此下面说的都是在RR级别中的。

以前一直搞不懂Gap Lock和Next-key Lock的区别,直到在网上看到一句话豁然开朗,但愿对各位有帮助。

Next-Key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。若是一个间隙被事务T1加了锁,其它事务是不能在这个间隙插入记录的。

干巴巴的说没意思,咱们来看看具体实例:

假设咱们有一张表:

| id | age  |

|  1 |    3 |

|  2 |    6 |

|  3 |    9 |

表结构以下:

CREATE TABLE test (
  id int(11) NOT NULL AUTO_INCREMENT,
  age int(11) DEFAULT NULL,
  PRIMARY KEY (id),
  KEY keyname (age)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=gbk ;

这样咱们age段的索引就分为

(negative infinity, 3],

(3,6],

(6,9],

(9,positive infinity);

咱们来看一下几种状况:

一、当事务A执行如下语句:

mysql> select * from fenye where age=6for update ;

不只使用行锁锁住了相应的数据行,同时也在两边的区间,(5,6]和(6,9] 都加入了gap锁。

这样事务B就没法在这个两个区间insert进新数据,可是事务B能够在两个区间外的区间插入数据。

二、当事务A执行

select * from fenye where age=7 for update ;

那么就会给(6,9]这个区间加锁,别的事务没法在此区间插入或更新数据。

三、若是查询的数据再也不范围内,

好比事务A执行 select * from fenye where age=100 for update ;

那么加锁区间就是(9,positive infinity)。

小结:

行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合造成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。

什么时候在InnoDB中使用表锁:

InnoDB在绝大部分状况会使用行级锁,由于事务和行锁每每是咱们选择InnoDB的缘由,可是有些状况咱们也考虑使用表级锁。

一、当事务须要更新大部分数据时,表又比较大,若是使用默认的行锁,不只效率低,并且还容易形成其余事务长时间等待和锁冲突。

二、事务比较复杂,极可能引发死锁致使回滚。

死锁:

咱们说过MyISAM中是不会产生死锁的,由于MyISAM老是一次性得到所需的所有锁,要么所有知足,要么所有等待。而在InnoDB中,锁是逐步得到的,就形成了死锁的可能。

在上面的例子中咱们能够看到,当两个事务都须要得到对方持有的锁才可以继续完成事务,致使双方都在等待,产生死锁。

发生死锁后,InnoDB通常均可以检测到,并使一个事务释放锁回退,另外一个获取锁完成事务。

避免死锁:

有多种方法能够避免死锁,这里只介绍常见的三种:

一、若是不一样程序会并发存取多个表,尽可能约定以相同的顺序访问表,能够大大下降死锁机会。

二、在同一个事务中,尽量作到一次锁定所须要的全部资源,减小死锁产生几率;

三、对于很是容易产生死锁的业务部分,能够尝试使用升级锁定颗粒度,经过表级锁定来减小死锁产生的几率;

相关文章
相关标签/搜索