
锁:
在单实例中,主要使用两种并发控制机制:Lock和Latch。
锁的本质是在数据并发访问(data concurrency)及数据一致性(data consistency)之间取得一个平衡。
隔离级别:
按数据隔离的级别来分,可以将锁分为共享锁和排它锁。
锁的范围:
按锁定数据的范围来看,可以分为行级锁、页级锁及表级锁。
锁的作用对象:
可以将锁锁分为DML及DDL锁。
锁的实现方式:
可以将锁分为悲观锁和乐观锁。
排它锁:
排它锁又称为写锁(Exclusive lock,简记为X锁),若事务T对数据对象A加上X锁,则其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。
它防止任何其它事务获取该资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。
排斥共享锁:
排它锁是一个独占锁,它不允许其它资源对其它进行任何形式的访问,也包括对其进行Select查询。
这是因为,如果当前事务正在对某个数据块进行更新操作。此时,正好有其它事务对其进行查询,那么必然得到脏数据。因而,Select是被禁止的。
但阻止用户查询该数据块显然是不合适的,Oracle使用的办法是在对该数据库加排它锁之前,先对其进行一个块备份,并保存在undo空间中。查询时,根据SCN的大小值比较去UNDO空间中去查询相关值。
排它锁的种类:
排它锁可以分成排它锁(X)以及行级排它锁(RX)两种。
排它锁是对当前整个表的数据范围执行DML操作,因而会对整个表加上一个排它锁。
行级排它锁是对表中某些记录执行DML操作,因而只会对某些行加上排它锁,其它记录可以正常操作。
悲观锁:
它认为在修改数据库数据的这段时间里存在着也想修改此数据的事务,对数据的冲突采取一种悲观的态度。所以,它在数据库开始读取的时候就把数据锁定住。
上锁时间点:数据库访问之时
悲观锁处理:
很多数据库的锁定通常采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制。在任何时间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完以后才能依次插入。其带来的后果就是性能的降低,在多用户并发访问的时候,当对一张表进行频繁操作时,会发现响应效率很低,数据库经常处于一种假死状态。
Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其余的数据不相干,所以在对Oracle表中并发插数据的时候,基本上不会有任何影响。
Oracle中的悲观锁:
Oracle中的悲观锁是利用Oracle的Connection对数据进行锁定。
在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不小心搞成死锁了就好。而且由于数据的及时锁定,在数据提交时候就不呼出现冲突,可以省去很多恼人的数据冲突处理。
其缺点就是你必须要始终有一条数据库连接,就是说在整个锁定到最后放开锁的过程中,你的数据库联接要始终保持住。
一般情况下,Oracle使用的是悲观锁。
共享锁:
共享锁又称为读锁(Share lock,简记为S锁),若事务T对数据对象A加上S锁,则其它事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。
共享锁:由非更新(读取)操作创建的锁。其他用户可以并发读取数据,但任何事务都不能获取数据上的排它锁,直到已释放所有共享锁。
共享锁排斥写锁:
共享锁的目的是保护读数据,使对块的读取值总是保持一致。
假设当前用户正在读取某个数据块的数据,此时,又有另一个事务对该块进行修改操作,必须会造成数据不一致。因此,必须对其加一个共享锁予以保护。
Oracel的做法保证了既能同时查看块的数据,又能对块进行修改。当某个块正在被读取时,另一个事务要修改这个表,它会首先复制这个数据块到undo空间。以后,数据的读取将直接从undo中读取,而修改动作则直接在当前块中完成。
Oracle对查询块加了一个共享锁,但同时又能做多种操作,就是因它,这些操作本身不是在同一个数据块上完成的。
共享锁的种类:
共享锁可以分成共享锁(S)以及行级共享锁(RS)两种。
共享锁是对当前整个表的数据范围执行Select操作,因而会对整个表加上一个共享锁。
行级共享锁是对表中某些记录执行Select操作,因而只会对某些行加上共享锁,其它记录可以正常操作。
由于Oracle本身实现机制的原因,在查询操作时不会产生共享锁。
乐观锁:
它认为在当前事务短暂的时间里不会有事务来修改此数据库的数据。所以,它只在数据提交更新的时候,才会正式对数据的冲突与否进行检测。如果发现冲突了,则让用户返回错误的信息,让用户决定如何去做。
上锁时间点:不上锁,只检测试数据
乐观锁存在的问题:
如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过 程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作 员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。
乐观锁的优点:
乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
乐观锁的处理方式:
1、第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。
2、第二种乐观锁的做法就是采用版本戳,采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。
当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。
3、第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。





