说到锁机制以前,先来看看Mysql的存储引擎,毕竟不一样的引擎的锁机制也随着不一样。mysql
MyIsam :不支持事务,不支持外键,因此访问速度快。锁机制是表锁,支持全文索引sql
InnoDB :支持事务、支持外键,因此对比MyISAM,InnoDB的处理效率差一些,并要占更多的磁盘空间保留数据和索引。锁机制是行锁,不支持全文索引数据库
Memory:数据是存放在内存中的,默认哈希索引,很是适合存储临时数据,服务器关闭后,数据会丢失掉。segmentfault
MyISAM:应用是以读操做和插入操做为主,只有不多的更新和删除操做,而且对事务的完整性、并发性要求不是很高。安全
InnoDB:用于事务处理应用程序,支持外键,若是应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致性。更新删除等频繁(InnoDB能够有效的下降因为删除和更新致使的锁定),对于数据准确性要求比较高的,此引擎适合。服务器
Memory:一般用于更新不太频繁的小表,用以快速获得访问结果。多线程
若是熟悉多线程,那么对锁确定是有概念的,锁是计算机协调多个进程或线程对某一资源并发访问的机制。并发
Mysql中的锁分为表锁和行锁:spa
顾名思义,表锁就是锁住一张表,而行锁就是锁住一行。线程
表锁的特色:开销小,不会产生死锁,发生锁冲突的几率高,而且并发度低。
行锁的特色:开销大,会产生死锁,发生锁冲突的几率低,并发度高。
所以MyISAM和Memory引擎采用的是表锁,而InnoDB存储引擎采用的是行锁。
分为共享读锁和独占写锁。
读锁是:当某一进程对某张表进行读操做时(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实现了两种类型的行锁。
共享锁(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的缘由,可是有些状况咱们也考虑使用表级锁。
一、当事务须要更新大部分数据时,表又比较大,若是使用默认的行锁,不只效率低,并且还容易形成其余事务长时间等待和锁冲突。
二、事务比较复杂,极可能引发死锁致使回滚。
咱们说过MyISAM中是不会产生死锁的,由于MyISAM老是一次性得到所需的所有锁,要么所有知足,要么所有等待。而在InnoDB中,锁是逐步得到的,就形成了死锁的可能。
在上面的例子中咱们能够看到,当两个事务都须要得到对方持有的锁才可以继续完成事务,致使双方都在等待,产生死锁。
发生死锁后,InnoDB通常均可以检测到,并使一个事务释放锁回退,另外一个获取锁完成事务。
有多种方法能够避免死锁,这里只介绍常见的三种:
一、若是不一样程序会并发存取多个表,尽可能约定以相同的顺序访问表,能够大大下降死锁机会。
二、在同一个事务中,尽量作到一次锁定所须要的全部资源,减小死锁产生几率;
三、对于很是容易产生死锁的业务部分,能够尝试使用升级锁定颗粒度,经过表级锁定来减小死锁产生的几率;