InnoDB存储引擎中的锁

下面对innodb存储引擎中的锁部分做一个简单的总结:

1、innodb存储引擎实现了如下的两种标准的行级锁

共享锁(s lock):允许事务读一行数据
排他锁(x lock):允许事务删除或者更新一行数据
注意 s与s,可以同时读一行数据
s与x则是互斥的、

2、由于innodb不止是能对行上锁,同时可以对整个表进行上锁,那么innodb还支持表级的锁。

innodb是可以同时允许这个两个锁执行的,对表的锁成为意向锁,也分为两种:

意向共享锁(IS LOCK):事务获得一个表的某几行的锁
意向排他锁(IX LOCK):事务要获得一个表中某几行的排他锁

3、innodb中支持的一致性非锁定读

它的意思是,如果某一行被使用了排他所,这个时候原则上是不能再去读该行的数据,但是这样会阻塞读操作,innodb提出了这种情况下,可以去读该行的快照数据,快照数据是旧数据,旧数据是不会被修改的,即不需要加锁,所以达到了锁和读并发操作,但是这个时候读到的数据是快照数据,不是修改之后的最新数据。

那么读快照的数据是哪个版本?

不同的事务隔离级别,那么读取的方式越是不同的,并不是所有的读是一致性读

在read commited 事务隔离级别,非一致性锁定读总是读最新版本的快照

在repeated read事务隔离级别,非一致性锁定读取事务开始时行数据对应的版本

事务隔离性不同的等级,会有不同的一致性等级,不是所有情况都需要保证到强一致性,如果弱一致性读出来,也可以通过其他方法在去发送重读,所以不同的等级对应不同的一致性问题。

4、那么在innodb中,行锁具体使用的是哪些锁的算法设计

Record lock:单个记录锁,即只上锁一行
Grap lock:间隙锁,锁定一个范围,但不包含技术本身
next-key lock:Record lock+Grap lock,锁定一个范围并且锁定记录本身

如果使用select对多行进行操作,那么触发的就是next-key lock这种锁算法
如果使用select对单行进行操作,那么触发的就是Record lock这种锁算法

5、innodb中锁是为了很好的让事务并发,同时可以保证事务的隔离性保证正确,那么在没有锁的情况下,事务操作可能发生哪几种错误呢?

丢失更新:没有锁的加入,同时更新一行记录,前一个更新回丢失
脏读:一个事务读到另外一个 事务没有提交的事务,即读到了脏数据
不可重复读:一个事务多次读到同一个数据,发现对应的值不一致
幻读:同一个事务中,同一个查询多次返回的结果不一致

不同的事务级别,对这种错误的忍耐级别也是不一样的。

————————————————————————————————————————————

丢失更新:

两个事务同时更新一行数据,最后一个事务的更新会覆盖掉第一个事务的更新,从而导致第一个事务更新的数据丢失,这是由于没有加锁造成的;

1. 脏读 :

脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。

e.g.
1.Mary的原工资为1000, 财务人员将Mary的工资改为了8000(但未提交事务)
2.Mary读取自己的工资 ,发现自己的工资变为了8000,欢天喜地!
3.而财务发现操作有误,回滚了事务,Mary的工资又变为了1000
像这样,Mary记取的工资数8000是一个脏数据。

2. 不可重复读 :

是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。

e.g.
1.在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
2.在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
3.在事务1中,Mary 再次读取自己的工资时,工资变为了2000
解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。

3. 幻读 :

是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。

e.g.
目前工资为1000的员工有10人。
1.事务1,读取所有工资为1000的员工。
2.这时事务2向employee表插入了一条员工记录,工资也为1000
3.事务1再次读取所有工资为1000的员工 共读取到了11条记录,
解决办法:如果在操作事务完成数据处理之前,任何其他事务都不可以添加新数据,则可避免该问题

不可重复读的重点是修改 :

同样的条件, 你读取过的数据,再次读取出来发现值不一样了

幻读的重点在于新增或者删除

同样的条件, 第 1 次和第 2 次读出来的记录数不一样

http://uule.iteye.com/blog/1109647
https://blog.csdn.net/jiesa/article/details/51317164
————————————————————————————————————————————

6、innodb不会发生锁升级,1个锁的开销与1000000个锁的开销是一样的,因为采用的next-key lock方式去加锁的。

其他的存储引擎,当单独行的锁的数量表多的时候,可能会发生锁的升级,即从一个行锁到页级的锁升级为一个表的锁。

7、数据库中常说的悲观锁和乐观锁是什么呢?

乐观锁:

实际不是一种锁,一种从业务逻辑上层次上,利用程序来处理并发,自己程序的逻辑保证,假定用户去读取某一个数据的时候,其他的用户不会来修改这个数据,然后最后在事务提交的时候,对开始读的版本和最后的版本进行对比,如果版本不一致,则有其他用户修改了数据,这个时候会返回提交错误的请求并进行回滚操作。

悲观锁:

完全依赖上面讲到的数据库的锁机制来完成的,在数据库中是可以使用repeatable read的隔离基表来实现悲观锁。具体的是当某一个用户读取数据的时候,其他用户可能也会对这个数据进行访问,所以在读取的时候会加锁,其他用户都不能访问,只有当自己释放了之后才能用。

8、悲观锁和乐观锁的优势和劣势?

乐观锁:
优势:

乐观锁机制避免了长事务中的数据库加锁解锁开销,大大提升了大并发量下的系统整体性能表现 所以如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以建议就要选择乐观锁定的方法,乐观锁页比较适合读比较多的情况。

劣势:

但是乐观锁也存在着问题,只能在提交数据时才发现业务事务将要失败,如果系统的冲突非常的多,而且一旦冲突就要因为重新计算提交而造成较大的代价的话,乐观锁也会带来很大的问题,在某些情况下,发现失败太迟的代价会非常的大。而且乐观锁也无法解决脏读的问题

悲观锁:
优势:

能避免冲突的发生,

劣势:

开销较大,而且加锁时间较长,对于并发的访问性支持不好。

9、对数据库中乐观、悲观锁,共享、排他锁,死锁的总结

并发控制:

事务和锁的存在都是为了更好的解决并发访问造成的数据不一致性的的问题
乐观锁和悲观锁都是为了解决并发控制问题, 乐观锁可以认为是一种在最后提交的时候检测冲突的手段,而悲观锁则是一种避免冲突的手段。

乐观锁:

是应用系统层面和数据的业务逻辑层次上的(实际上并没有加锁,只不过大家一直这样叫而已),利用程序处理并发, 它假定当某一个用户去读取某一个数据的时候,其他的用户不会来访问修改这个数据,但是在最后进行事务的提交的时候会进行版本的检查,以判断在该用户的操作过程中,没有其他用户修改了这个数据。开销比较小

乐观锁的实现大部分都是基于版本控制实现的, 除此之外,还可以通过时间戳的方式,通过提前读取,事后对比的方式实现 写到这里我突然想起了,java的cuurent并发包里的Automic 类的实现原理CAS原理(Compare and Swap), 其实也可以看做是一种乐观锁的实现,通过将字段定义为volalate,(不允许在线程中保存副本,每一次读取或者修改都要从内存区读取,或者写入到内存中), 通过对比应该产生的结果和实际的结果来进行保证原子操作,进行并发控制(对比和交换的正确性保证 是处理器的原子操作)。

乐观锁的优势和劣势
优势:

如果数据库记录始终处于悲观锁加锁状态,可以想见,如果面对几百上千个并发,那么要不断的加锁减锁,而且用户等待的时间会非常的长, 乐观锁机制避免了长事务中的数据库加锁解锁开销,大大提升了大并发量下的系统整体性能表现 所以如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以建议就要选择乐观锁定的方法, 而如果并发量不大,完全可以使用悲观锁定的方法。乐观锁也适合于读比较多的场景。

劣势:

但是乐观锁也存在着问题,只能在提交数据时才发现业务事务将要失败,如果系统的冲突非常的多,而且一旦冲突就要因为重新计算提交而造成较大的代价的话,乐观锁也会带来很大的问题,在某些情况下,发现失败太迟的代价会非常的大。而且乐观锁也无法解决脏读的问题

同时我在思考一个问题,乐观锁是如何保证检查版本,提交和修改版本是一个原子操作呢? 也就是如何保证在检查版本的期间,没有其他事务对其进行操作?

解决方案: 将比较,更新操作写入到同一条SQL语句中可以解决该问题,
比如

update table1 set a=1, b=2, version = version +1 where version = 1; 

mysql 自己能够保障单条SQL语句的原子操作性。

如果是多条SQL语句,就需要mySQL的事务通过锁机制来保障了


悲观锁:

完全依赖于数据库锁的机制实现的,在数据库中可以使用Repeatable Read的隔离级别(可重复读)来实现悲观锁,它完全满足悲观锁的要求(加锁)。

它认为当某一用户读取某一数据的时候,其他用户也会对该数据进行访问,所以在读取的时候就对数据进行加锁, 在该用户读取数据的期间,其他任何用户都不能来修改该数据,但是其他用户是可以读取该数据的, 只有当自己读取完毕才释放锁。

悲观锁的优势和劣势

劣势:开销较大,而且加锁时间较长,对于并发的访问性支持不好。
优势 : 能避免冲突的发生

我们经常会在访问数据库的时候用到锁,怎么实现乐观锁和悲观锁呢?

以hibernate为例,可以通过为记录添加版本或时间戳字段来实现乐观锁,一旦发现出现冲突了,修改失败就要通过事务进行回滚操作。可以用session.Lock()锁定对象来实现悲观锁(本质上就是执行了SELECT * FROM t FOR UPDATE语句)

乐观锁和悲观所各有优缺点,在乐观锁和悲观锁之间进行选择的标准是:==发生冲突的频率与严重性。 ==

如果冲突很少,或者冲突的后果不会很严重,那么通常情况下应该选择乐观锁,因为它能得到更好的并发性,而且更容易实现。但是,如果冲突太多或者冲突的结果对于用户来说痛苦的,那么就需要使用悲观策略,它能避免冲突的发生。 如果要求能够支持高并发,那么乐观锁。
其实使用乐观锁 高并发==高冲突, 看看你怎么衡量了。

但是现在大多数源代码开发者更倾向于使用乐观锁策略

共享锁和排它锁是具体的锁,是数据库机制上的锁。

共享锁(读锁)

在同一个时间段内,多个用户可以读取同一个资源,读取的过程中数据不会发生任何变化。读锁之间是相互不阻塞的, 多个用户可以同时读,但是不能允许有人修改, 任何事务都不允许获得数据上的排它锁,直到数据上释放掉所有的共享锁

排它锁(写锁)

在任何时候只能有一个用户写入资源,当进行写锁时会阻塞其他的读锁或者写锁操作,只能由这一个用户来写,其他用户既不能读也不能写。
加锁会有粒度问题,从粒度上从大到小可以划分为

表锁

开销较小,一旦有用户访问这个表就会加锁,其他用户就不能对这个表操作了,应用程序的访问请求遇到锁等待的可能性比较高。

页锁:

是MySQL中比较独特的一种锁定级别,锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。

行锁

开销较大,能具体的锁定到表中的某一行数据,但是能更好的支持并发处理, 会发
生死锁

你可能感兴趣的:(InnoDB存储引擎)