数据库锁和事务浅析

锁的介绍

1.什么是乐观锁?

       通俗的理解,太乐观了,总是认为我们对数据操作时,不会产生冲突问题。也就是说在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。一般来说数据库不实现乐观锁,需要我们自己去实现。

       乐观锁中常见的实现机制:先给数据表加一个版本(version)字段,操作一次,将那条记录的版本号加1。

2.什么是悲观锁?

       通俗的理解,太悲观了,总是认为我们对数据操作时,会产生冲突问题。也就是说每次操作时,都要通过获取锁才能进行对相同数据的操作。数据库本身实现了悲观锁。

 有哪些悲观锁?

2.1共享锁:又称读锁,是读取操作创建的锁。其他事务可以并发读取数据,但是不能对数据进行修改,直到释放锁。

共享锁SQL实现机制:SELECT... LOCK IN SHARE MODE;

2.2 排它锁:又称写锁,如果事务T对数据D加上排他锁后,则其他事务不能再对D加任任何类型的锁。获准排他锁的事务既 能读数据,又能写数据。

 排他锁SQL实现机制:SELECT ... FOR UPDATE;

2.3行锁:对数据库中的“读写操作的行”加锁

2.4表锁:对数据库中的“读取操作的表”加锁

一、共享锁

1、多个事务的查询语句可以共用一把共享锁;

2、如果只有一个事务拿到了共享锁,则该事务可以对数据进行 update、delete等操作;

3、如果有多个事务拿到了共享锁,则所有事务都不能对数据进行update、delete等操作。

二、排他锁

排他锁又称为写锁,简称X锁,顾名思义,排他锁不能与其他锁并存,并且只有一个事务能拿到某一数据行的排他锁,其余事务不能再获取该数据行的所有锁。

结论

1、只有一个事务能获取该数据的排他锁;

2、一旦有一个事务获取了该数据的排他锁之后,其余事务对于该数据的操作将会被阻塞,直到锁释放。

三、自增锁

自增锁是mysql一种特殊的锁,如果表中存在自增字段,mysql便会自动维护一个自增锁。

注意:

1、如果上述三次查询放到一个事务中,在RR事务隔离级别下,是不会出现幻读的情况;

2、自增锁不仅是只对insert

into语句才会出现,还对其他的插入语句生效。



记录锁、间隙锁、临键锁,都属于排它锁;

记录锁就是锁住一行记录;

间隙锁只有在事务隔离级别 RR 中才会产生;

唯一索引只有锁住多条记录或者一条不存在的记录的时候,才会产生间隙锁,指定给某条存在的记录加锁的时候,只会加记录锁,不会产生间隙锁;

普通索引不管是锁住单条,还是多条记录,都会产生间隙锁;

间隙锁会封锁该条记录相邻两个键之间的空白区域,防止其它事务在这个区域内插入、修改、删除数据,这是为了防止出现 幻读 现象;

普通索引的间隙,优先以普通索引排序,然后再根据主键索引排序(多普通索引情况还未研究);

事务级别是RC(读已提交)级别的话,间隙锁将会失效。


锁优化的建议:

1、尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁

2、合理设计索引,尽量缩小锁的范围

3、尽可能减少检索条件范围,避免间隙锁

4、尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行

5、尽可能低级别事务隔离



什么是事务

事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取。事务的正确执行使得数据库从一种状态转换为另一种状态。

事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)、持久性(durability)的缩写,这四种状态的意思是:

1、原子性

即不可分割,事务要么全部被执行,要么全部不执行。如果事务的所有子事务全部提交成功,则所有的数据库操作被提交,数据库状态发生变化;如果有子事务失败,则其他子事务的数据库操作被回滚,即数据库回到事务执行前的状态,不会发生状态转换

2、一致性

事务的执行使得数据库从一种正确状态转换成另外一种正确状态

3、隔离性

在事务正确提交之前,不允许把事务对该数据的改变提供给任何其他事务,即在事务正确提交之前,它可能的结果不应该显示给其他事务

4、持久性

事务正确提交之后,其结果将永远保存在数据库之中,即使在事务提交之后有了其他故障,事务的处理结果也会得到保存。


事务安全性问题:

1、脏写/读:无论是脏写还是脏读,都是因为一个事务去更新或者查询了另外一个还没提交的事务更新过的数据。因为另外一个事务还没提交,所以它随时可能会回滚,那么必然导致你更新的数据就没了,或者你之前查询到的数据就没了,这就是脏写和脏读两种场景。

2、不可重复读:事务A多次读取同一数据,事务B在事务A多次读取的过程中,对数据做了更新并提交,导致事务A多次读取同一数据时,结果不一致;

3、幻读:事务A在更新数据时,事务B插入数据,导致事务A执行完发现数据没有完全更新。


事务隔离级别

事务隔离级别,就是为了解决上面几种问题而诞生的。为什么要有事务隔离级别,因为事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡。所以设立了几种事务隔离级别,以便让不同的项目可以根据自己项目的并发情况选择合适的事务隔离级别,对于在事务隔离级别之外会产生的并发问题,在代码中做补偿。

   数据库的事务级别我们可以理解为按照锁来划分的。主要有3个锁的要素:锁的类型、锁的释放、锁的范围。

      1.锁的类型:共享锁、排他锁。具体介绍看第一部分

      2.锁的释放:2.1在操作数据上释放锁。对数据的读写操作完成后释放锁,此时如果操作数据的这个事务尚未结束,其他事务是可以继续操作数据的。2.2在操作数据的事务上释放锁。对数据的读写操作完成后并未释放锁,此时要等操作数据的这个事务结束后,其他事务才可以操作这个数据。

      3.锁的范围:3.1对操作的数据上锁。操作哪一行数据,就对哪一行数据上锁。3.2对操作的数据的表上锁。操作那个表的数据,就对那个表上锁。


一级:未提交读(Read uncommitted)

原理:锁的类型:读取共享锁

锁的释放:读取数据结束,释放锁

锁的范围:对读取的数据上锁

引发的安全性:虚读,不可重复读,幻读

表现:读取数据未结束时,不释放锁,谁都可以读,但是不能写;读取数据结束,释放锁,但事务尚未结束的时候,其他事务可以随意读写,因为此时锁释放了,事务却没有结束,所以什么安全性问题都可能引发。

二级:已提交读(Read committed)

原理:锁的类型:读取共享锁,修改排他锁

锁的释放:读取数据结束,释放锁

修改数据的事务结束,释放锁

锁的范围:对操作的数据上锁

引发的安全性:不可重复读,幻读

表现:读取操作和一级事务一致,修改数据的事务在没有结束时,所有的事务都不可以修改,因为此时排它锁还未释放。因此解决了脏读。

三级:可重复读(Repeatable read)

原理:锁的类型:读取共享锁,修改排他锁

锁的释放:读取数据的事务结束,释放锁

修改数据的事务结束,释放锁

锁的范围:对操作的数据上锁

引发的安全性:幻读

表现:读取数据的事务在没有结束时,所有的事务都不可以修改数据,因为此时读取锁还未释放。修改数据的事务在没有结束时,所有的事务都不可以修改数据,因为此时排他锁还未释放。因此解决了不可重读

四级:序列化(Serializable)

原理:锁的类型:读取共享锁,修改排他锁

锁的释放:读取数据的事务结束,释放锁

修改数据的事务结束,释放锁

锁的范围:对操作的表上锁

引发的安全性:无(至少上述三种安全性)

表现:这个比较好理解,对表上锁了,而且都是读写的事务结束以后才释放锁,所以要写的前提是,先拿到锁。什么时候能拿到锁?读写数据的事务结束以后。所以能解决目前所知的所有安全性问题。但是这样的话读写效率会很低。


图1 总结表格


图2 总结表格

再必须强调一遍,不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容