Rock_R Newtol
html
咱们在以前介绍的都是关于只有一个用户操做数据库的状况,可是在实际的项目中,面临的更多状况是多用户操做数据库。例如电商平台的秒杀系统等,可能会在短期具备多个用户对数据库进行操做,若是没有进行特殊的处理,这是极其容易形成数据冲突的。咱们下面举例说明一下发生冲突的一种状况mysql
如图,A用户和C用户共有一个余额为1000元的公用帐户,这天,A和C同时到了不一样银行,A和C同时得到了帐户的余额信息,这时间A就决定往帐户中存入1000元,C决定从帐户中取出1000元。正常状况下:帐户余额依旧为1000元。可是若是不采起措施,那么数据库只会保存A用户的2000元的余额信息。这是为啥呢?当A和C拿到余额信息时,帐户余额为1000元。假设C用户取款须要1min,A用户存款须要2min.那么当C用户取款成功后,帐户余额被更改成0。可是此时的用户A并不知道。她仍然是按照帐户还有1000元的基础上进行操做,那么当他反馈给帐户的时候,1min前余额才被更改成0的帐户就会被更改成2000。因此为了不这样的状况发生,就必须使用锁定。锁定是为了当某个用户在进行操做而拒绝其余用户操做的一种机制,解除锁定时被称为解锁。 加锁之后的流程大体为:sql
按照使用的目的能够分为:数据库
共享锁(Shared Lock,也叫S锁): 共享锁是当用户参照数据时,将数据对象变为只读形式的锁定。例如在上面的流程中,A用户第一次获取帐户余额信息的行为并不会被拒绝,可是,A用户在C用户结束操做以前,依然是没法对数据进行修改的。也被称为:读取锁定性能优化
排他锁 (Exclusive Lock,也叫X锁): 排他锁是使用【INSERT】,【UPDATE】,【DELETE】命令对数据进行修改时,使用的机制。例如在上面的流程中,使用的就是排他锁,即A用户的第一次读取操做也会被拒绝。只有当C用户完成全部操做之后,A用户才能进行操做。也被称为写入锁定或者独占锁定bash
为了容许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。session
意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。架构
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。并发
表锁(数据表): 开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突几率高,并发度最低,通常是作ddl处理时使用。高并发
行锁(记录): 开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的几率低,并发度高,MySQL通常都是用行锁来处理并发事务
页锁(数据库): 开销和加锁速度介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间,并发度通常
锁定的粒度会影响程序的并发数。通常状况下,锁定的粒度越小,并发性才会更高。例如:在使用了行锁的状况下,还能够对同一数据表的不一样行进行数据的处理,而若是使用了表锁定,其余进程只能等到前一个进程完成了事务处理后才能进行操做。那么是否是锁定的粒度越小越好呢?其实否则,由于锁定会极大的消耗着数据的资源,也就是说,锁定的数目越多,消耗的服务的资源也就越多。
若是数据库中行单位粒度的锁定大量发生的状况时,数据库有将这些锁定的粒度自动向上提高的机制,被称为锁定提高(Lock Escalation)
根据不一样的存储引擎,MySQL中锁的特性能够大体概括以下:
存储引擎 | 行锁 | 表锁 | 页锁 |
---|---|---|---|
MyISAM | √ | ||
BDB | √ | √ | |
InnoDB | √ | √ |
目前主要有两种锁定协议:
一段锁协议:为了预防在高并发的环境中发生死锁的状况,事先就将须要使用到数据所有进行锁定,等全部操做结束后再进行解锁。
两段锁协议: 将事务分红两个阶段,加锁阶段和解锁阶段。
加锁阶段:在对任何数据进行读操做以前要申请并得到共享锁(其它事务能够继续加共享锁,但不能加排它锁),在进行写操做以前要申请并得到排它锁(其它事务不能再得到任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
解锁阶段:当事务释放了一个封锁之后,事务进入解锁阶段,在该阶段只能进行解锁操做不能再进行加锁操做。
InnoDB采用的是两阶段锁定协议,由于在事务开始阶段,数据库并不知道会用到哪些数据。在事务执行过程当中,随时均可以执行锁定,锁只有在执行 COMMIT或者ROLLBACK的时候才会释放,而且全部的锁是在同一时刻被释放。前面描述的锁定都是隐式锁定,InnoDB会根据事务隔离级别在须要的时候自动加锁。
须要更新或插入大量数据且表结构又比较复杂。在这种状况是使用行锁很容易形成锁冲突和长时间的等待。
事务涉及多表的操做,若是在这种状况使用行锁,很容易引发死锁,形成大量的事务回滚。
若是屡次涉及到上述两种事务情形,可根据实际状况考虑是否使用MyISAM引擎。
在用【 LOCK TABLES】对【InnoDB】表加锁时要注意,要将【AUTOCOMMIT】设为0,不然MySQL不会给表加锁;事务结束前,不要用【UNLOCK TABLES】释放表锁,由于【UNLOCK TABLES】会隐含地提交事务;【COMMIT】或【ROLLBACK】并不能释放用【LOCK TABLES】加的表级锁,必须用【UNLOCK TABLES】释放表锁。
使用【LOCK TABLES】虽然能够给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,仅当【autocommit】为0、【InnoDB
在InnoDB引擎中行锁是经过给索引上的索引项加锁来实现的,也就意味着只有经过索引条件检索数据,InnoDB才使用行级锁,不然,InnoDB将使用表锁
行锁是针对索引加的锁,不是针对记录加的锁,因此虽然是访问不一样行的记录,可是若是是使用相同的索引键,是会出现锁冲突的。
当表有多个索引的时候,不一样的事务可使用不一样的索引锁定不一样的行,另外,不管是使用主键索引、惟一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
即使在条件中使用了索引字段,可是否使用索引来检索数据是由MySQL经过判断不一样执行计划的代价来决定的,若是MySQL认为全表扫描效率更高,好比对一些很小的表,它就不会使用索引,这种状况下InnoDB将使用表锁,而不是行锁。所以,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。
它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以,在整个数据处理过程当中,将数据处于锁定状态。简要说就是:每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会进入等待状态。例如:Java中的【synchronized】 就属于悲观锁的一种实现,每次线程要修改数据时都先得到锁,保证同一时刻只有一个线程能操做数据。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)。
它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持开放态度。每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据。悲观锁大多数状况下依靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。而乐观锁机制在必定程度上解决了这个问题,乐观锁适用于读多写少的应用场景,这样大大提升吞吐量。乐观锁主要的实现方式有:
使用数据版本(Version)记录机制实现,这是乐观锁最经常使用的一种实现方式。何谓数据版本?即为数据增长一个版本标识,通常是经过为数据库表增长一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值+1。当咱们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,若是数据库表当前版本号与第一次取出来的version值相等,则予以更新,不然认为是过时数据。
使用时间戳(timestamp)。乐观锁定的第二种实现方式和第一种差很少,一样是在须要乐观锁控制的table中增长一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。
在多任务系统下,当一个或多个进程等待系统资源,而资源又被进程自己或其余进程占用时,就造成了死锁。在数据库中,由于MyISAM老是一次性得到所需的所有锁,所以不会出现死锁。因此死锁主要发生于InnoDB引擎中。可是,发生死锁后,InnoDB通常都能自动检测到,并使一个事务释放锁并回退,另外一个事务得到锁,继续完成事务。但在涉及外部锁,或涉及表锁的状况下,InnoDB并不能彻底自动检测到死锁,这须要经过设置锁等待超时参数【innodb
在应用中,若是不一样的程序会并发存取多个表,应尽可能约定以相同的顺序为访问表,这样能够大大下降产生死锁的机会。若是两个session访问两个表的顺序不一样,发生死锁的机会就很是高!但若是以相同的顺序来访问,死锁就可能避免。
在程序以批量方式处理数据的时候,若是事先对数据排序,保证每一个线程按固定的顺序来处理记录,也能够大大下降死锁的可能。 例如:
Session1:mysql> select * from test where id in (8,9) for update;+----+--------+------+| id | course | name | +----+--------+------+| 8 | XXX | xxx | | 9 | FFF | fff | +----+--------+------+2 rows in set (0.04 sec)Session2:select * from test where id in (10,8,5) for update;//锁等待中……//其实这个时候id=10这条记录没有被锁住的,但id=5的记录已经被锁住了,锁的等待在id=8的这里。Session3:mysql> select * from test where id=5 for update;//锁等待中Session4:mysql> select * from test where id=10 for update;+----+--------+------+| id | course | name | +----+--------+------+| 10 | KKK | kkk | +----+--------+------+1 row in set (0.00 sec)在其它session中id=5是加不了锁的,可是id=10是能够加上锁的。复制代码
在开发中,使用【insert into test(xx,xx) on duplicate key update xx
='XX';】来解决:根据字段值查询(有索引),若是不存在,则插入;不然更新的需求。不然很容易出现死锁,例如:
以id为主键为例,目前尚未id=22的行Session1:select * from t3 where id=22 for update;Empty set (0.00 sec)session2:select * from t3 where id=23 for update;Empty set (0.00 sec)Session1:insert into t3 values(22,'ac','a',now());锁等待中……Session2:insert into t3 values(23,'bc','b',now());ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction复制代码
到这里,咱们就讲解完了关于锁与事务处理分离水平的第一部分的内容,后面咱们将介绍关于事务处理分离水平。
美团点评技术团队(ameng ·2014-08-20 15:50).Innodb中的事务隔离级别和锁的关系 博文地址:https://tech.meituan.com/innodb_lock.html
《MySQL性能优化与架构设计》
文章开发于公众号【Newtol】,关注做者我的技术公众号,有更多视频学习资源分享