ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

mysql-锁

2019-06-30 16:38:18  阅读:123  来源: 互联网

标签:update 索引 mysql 操作 where id SELECT


一、锁概述

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中的表锁兼容性 。

 1.png

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、悲观锁

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

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

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

标签:update,索引,mysql,操作,where,id,SELECT
来源: https://www.cnblogs.com/ivy-zheng/p/11109468.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有