mysql-锁

Posted ivy-zheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql-锁相关的知识,希望对你有一定的参考价值。

一、锁概述

1、相对其他数据库而言,mysql的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,

(1)MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);

(2)BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;

(3)InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。 

2、MySQL这3种锁的特性可大致归纳如下。开销、加锁速度、死锁、粒度、并发性能。

(1)表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

(2)行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

(3)页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

仅从锁的角度来说:表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。

从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适!

二、My ISAM表锁

1、MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。

2、MySQL中的表锁兼容性 。

 技术图片

3、给表加锁

(1)加共享读锁 lock table 表名 read。

(2)表独占写锁lock table 表名 write。

4、说明:

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁

MyISAM表的读操作与写操作之间,以及写操作之间是串行的!

(1)对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;

(2)对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;

(3)MyISAM表的读操作与写操作之间,以及写操作之间是串行的!当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。

(4)一个session使用LOCK TABLE命令给表f加了读锁,这个session可以查询锁定表中的记录,但更新或访问其他表都会提示错误;同时,另外一个session可以查询表中的记录,但更新就会出现锁等待。

5、并发插入(Concurrent Inserts)

由于MySQL认为写请求一般比读请求要重要,所以如果有读写请求同时进行的话,MYSQL将会优先执行写操作。这样MyISAM表在进行大量的更新操作时(特别是更新的字段中存在索引的情况下),会造成查询操作很难获得读锁,从而导致查询阻塞。 

MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

1)当concurrent_insert设置为0时,不允许并发插入。

2)当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置

3)当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

解决方案大概有如下几种:

(1)使用--low-priority-updates启用mysqld。这将给所有更新(修改)一个表的语句以比SELECT语句低的优先级。
   在这种情况下,在先前情形的最后的SELECT语句将在INSERT语句前执行。 (2)为max_write_lock_count设置一个低值,使得在一定数量的WRITE锁定后,给出READ锁定 (3)使用LOW_PRIORITY属性给于一个特定的INSERT,UPDATE或DELETE较低的优先级 (4)使用HIGH_PRIORITY属性给于一个特定的SELECT (5)使用INSERT DELAYED语句

6、My ISAM锁的调度

(1)思考?

 一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁?Mysql如何处理呢?

答案:写进程先获得锁,即使读请求先到锁等待队列,写请求后到,写请起后到,写锁也会插到读锁队列。Mysql认为写请求比读请求更重要。MyISAM不太适合于有大量更新操作和查询操作的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。读的这个进程会一直等到这个写的进程完成之后,它才会得到这个请求。

(2)解决方法

执行 set low_priority_updates =1;使该连接发出的更新请求优先级降低。它的优先级降低那么实际上这个查询操作就更能获得我们这个锁。 其中insert,delete也可以通过此种方法指定。

三、InnoDB行锁和表锁

1、InnoDB行锁是通过给索引上的索引项加锁来实现的,InnoDB这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!

2、行锁

  • 读锁:允许其他线程上读锁,但是不允许上写锁。
    lock in share mode
    如: select * from user where user_name=‘wzzf‘ lock in share mode
  • 写锁:不允许其他线程上任何锁。
    1: FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效
    for update
    如:select * from user where user_name=‘wzzf‘ for update

3、行锁必须要索引才能实现,否则会自动锁全表,两个事务可以用同一个索引,下面给出例子: 
读锁由于不排除其他线程再加读锁比较难测试,所以下面用写锁测试,先测试没加所以字段进行加锁,对不同记录进行加锁,如果都加锁成功说明是加了行锁,反之则是默认锁全表。

(1)下面的consumer_chain_order_number是不会重复的,但没有索引

-- 马上显示查询结果
BEGIN;
SELECT * from tyg_consumer_chain_sell_order o where o.consumer_chain_order_number=26911523448454 for update


-- 一直没有结束知道等待超时
BEGIN;
SELECT * from tyg_consumer_chain_sell_order o where o.consumer_chain_order_number=55181523448554 for update

   说明了默认锁全表

(2)接下来试下有索引的字段--这个表的主键

BEGIN;
SELECT * from tyg_consumer_chain_sell_order o where o.consumer_chain_order_id=1 for update


BEGIN;
SELECT * from tyg_consumer_chain_sell_order o where o.consumer_chain_order_id=2 for update

都查询出来了结果,说明锁住了行

4、由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点。

mysql> select * from tab_with_index where id = 1 and name = ‘1‘ for update;
+------+------+ | id | name | +------+------+ | 1 | 1 | +------+------+ 1 row in set (0.00 sec) 虽然session_2访问的是和session_1不同的记录,但是因为使用了相同的索引,所以需要等待锁: mysql> select * from tab_with_index where id = 1 and name = ‘4‘ for update; 等待

两个事务不能锁同一个索引

事务A先执行:
select math from zje where math>60 for update;

事务B再执行:
select math from zje where math<60 for update;
这样的话,事务B是会阻塞的。如果事务B把 math索引换成其他索引就不会阻塞,
但注意,换成其他索引锁住的行不能和math索引锁住的行有重复。

5、当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。

表tab_with_index的id字段有主键索引,name字段有普通索引:
mysql> select * from tab_with_index where id = 1 for update;
+------+------+
| id   | name |
+------+------+
| 1    | 1    |
| 1    | 4    |
+------+------+
2 rows in set (0.00 sec)


Session_2使用name的索引访问记录,因为记录没有被索引,所以可以获得锁:
mysql> select * from tab_with_index where name = ‘2‘ for update;
+------+------+
| id   | name |
+------+------+
| 2    | 2    |
+------+------+
1 row in set (0.00 sec)


由于访问的记录已经被session_1锁定,所以等待获得锁。:
mysql> select * from tab_with_index where name = ‘4‘ for update;

6、即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。

7、insert ,delete , update在事务中都会自动默认加上排它锁。select则要显示加上相应的锁 

由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例) ,
否则MySQL将会执行Table Lock (将整个资料表单给锁住)。 举个例子
: 假设有个表单products ,里面有id跟name二个栏位,id是主键。 例1: (明确指定主键,并且有此笔资料,row lock) SELECT * FROM products WHERE id=‘3‘ FOR UPDATE; SELECT * FROM products WHERE id=‘3‘ and type=1 FOR UPDATE; 例2: (明确指定主键,若查无此笔资料,无lock) SELECT * FROM products WHERE id=‘-1‘ FOR UPDATE; 例2: (无主键,table lock) SELECT * FROM products WHERE name=‘Mouse‘ FOR UPDATE; 例3: (主键不明确,table lock) SELECT * FROM products WHERE id<>‘3‘ FOR UPDATE; 例4: (主键不明确,table lock) SELECT * FROM products WHERE id LIKE ‘3‘ FOR UPDATE; 注1: FOR UPDATE仅适用于InnoDB,且必须在交易区块(BEGIN/COMMIT)中才能生效。
假设kid 是表table 的 一个索引字段 且值不唯一
1.如果kid 有多个值为12的记录那么:
update table  set name=’feie’ where kid=12;  
会锁表
2.如果kid有唯一的值为1的记录那么:
update table  set name=’feie’ where kid=1;  
不会锁表
总结:用索引字段做为条件进行修改时, 是否表锁的取决于这个索引字段能否确定记录唯一,
当索引值对应记录不唯一,会进行锁表,相反则行锁。
 
如果有两个delete
 
kid1 与 kid2是索引字段
语句1 delete from table where  kid1=1 and kid2=2;
语句2 delete from table where  kid1=1 and kid2=3;
这样的两个delete 是不会锁表的
语句1 delete from table where  kid1=1 and kid2=2;
语句2 delete from table where  kid1=1 ;
这样的两个delete 会锁表
总结:同一个表,如果进行删除操作时,尽量让删除条件统一,否则会相互影响造成锁表

8、意向锁

  意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。

  意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。

  所以,可以说InnoDB的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),我们可以通过以下表格来总结上面这四种所的共存逻辑关系:

技术图片

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

对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁;

 三、乐观锁和悲观锁

1、乐观锁

  乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

  通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段,如果要对那条记录进行操作(更新),则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

举例:

下单操作包括3步骤:

(1)查询出商品信息

  select (status,status,version) from t_goods where id=#id

(2)根据商品信息生成订单

(3)修改商品status为2

  update t_goods 

  set status=2,version=version+1

  where id=#id and version=#version;
除了自己手动实现乐观锁之外,现在网上许多框架已经封装好了乐观锁的实现

2、悲观锁

与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。

共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴

以上是关于mysql-锁的主要内容,如果未能解决你的问题,请参考以下文章

怎么给mysql加排他锁

mysql插入意向锁测试

Mysql加锁过程详解-基本知识

MySQL 进阶 锁 -- MySQL锁概述MySQL锁的分类:全局锁(数据备份)表级锁(表共享读锁表独占写锁元数据锁意向锁)行级锁(行锁间隙锁临键锁)

关于mysql 共享锁和排他锁 互斥问题?

mysql 锁机制