并发一致性问题
在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。
丢失修改
丢失修改指一个事务的更新操作被另外一个事务的更新操作替换。一般在现实生活中常会遇到,例如:T1 和 T2 两个事务都对一个数据进行修改,T1 先修改并提交生效,T2 随后修改,T2 的修改覆盖了 T1 的修改。
丢失修改(Lost Update):假设有两个用户A和B同时对数据库中的某一数据进行修改,并且A先提交了修改,然后B也提交了修改,那么A的修改就会被B的修改覆盖,从而导致A的修改丢失。例如,A和B同时对某一商品的库存进行修改,A将库存从100减少到90并提交了修改,然后B也将库存从100减少到95并提交了修改,那么最终库存会变成95而不是90。
读脏数据
读脏数据指在不同的事务下,当前事务可以读到另外事务未提交的数据。例如:T1 修改一个数据但未提交,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据
读脏数据(Dirty Read):假设有一个用户A对数据库中的某一数据进行修改,并且在修改过程中,另一个用户B对该数据进行了查询,那么B就可能会读到未提交的“脏数据”,从而导致数据的不一致性。例如,A对某一商品的价格进行修改,将价格从100元增加到120元,但是还没有提交修改,此时B查询该商品的价格时,发现价格已经变成了120元,但是实际上A的修改还没有提交,因此B读到了未提交的“脏数据”。
不可重复读
不可重复读指在一个事务内多次读取同一数据集合。在这一事务还未结束前,另一事务也访问了该同一数据集合并做了修改,由于第二个事务的修改,第一次事务的两次读取的数据可能不一致。例如:T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。
产生并发不一致性问题的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
不可重复读(Non-repeatable Read):假设有一个用户A对数据库中的某一数据进行查询,并且在查询过程中,另一个用户B对该数据进行了修改,那么A再次查询该数据时就会发现数据与之前不一致。例如,A查询某一商品的价格为100元,然后B将该商品的价格修改为110元,并提交了修改,此时A再次查询该商品的价格时,发现价格已经变成了110元,与之前不一致。
解决该问题
丢失修改:由于事务在修改数据R之前必须加上X锁,其他事务无法同时修改R,因此可以避免丢失修改的问题。
不可重复读:由于事务在读取数据R之前必须加上S锁,其他事务无法同时修改或删除R,因此可以避免不可重复读的问题
读脏数据:由于事务在读取数据R之前必须加上S锁,其他事务无法同时修改或删除R,因此可以避免读脏数据的问题。
存在的问题:缺点是锁的粒度较大,可能会导致资源的浪费和性能的下降
两段封锁协议
两段封锁协议是一种并发控制方法,它要求事务必须分两个阶段对数据项进行加锁和解锁操作。这两个阶段分别是封锁阶段和解封锁阶段,具体如下:
封锁阶段:在该阶段中,事务可以获得任何数据项上的任何类型的锁,但不能释放锁。在该阶段,事务需要先获得所有需要使用的锁,然后才能进行相应的操作,例如读取或修改数据。该阶段的目的是保证在事务对数据进行操作时,其他事务无法修改或删除数据,从而避免数据不一致的情况。
解封锁阶段:在该阶段中,事务可以释放任何数据项上的任何类型的锁,但不能申请新的锁。在该阶段,事务需要先释放所有不再需要使用的锁,然后才能提交事务或者回滚事务。该阶段的目的是释放事务在该阶段中,事务可以释放任何数据项上的任何类型的锁,但不能申请新的锁。在该阶段,事务需要先释放所有不再需要使用的锁,然后才能提交事务或者回滚事务。该阶段的目的是释放事务占用的资源,避免资源的浪费。
死锁/活锁
活锁是指在并发访问的过程中,由于某些原因,事务一直处于等待状态,但是等待的条件不满足,导致事务无法继续执行。例如,在两个事务同时请求封锁同一条数据时,如果它们始终无法获取到该数据的封锁,就会导致活锁的情况发生。在活锁的情况下,事务一直在运行,但是无法完成任务,导致系统资源的浪费和性能的下降。
死锁是指在并发访问的过程中,由于多个事务之间相互等待对方持有的锁,导致多个事务都无法继续执行,从而形成了一个死循环。例如,如果事务A请求封锁数据R1后又请求封锁数据R2,而事务B则请求封锁数据R2后又请求封锁数据R1,这样就会导致事务A和事务B之间形成了一个死锁。在死锁的情况下,事务都无法继续执行,只能等待其他事务释放锁,从而导致系统的资源浪费和性能下降。
封锁协议可以解决死锁的问题,封锁协议可以通过以下两种方式来避免死锁的情况:
-
顺序加锁:在多个事务同时访问多个数据项时,按照固定的顺序对数据项进行加锁,从而避免不同事务之间加锁的顺序不一致,导致死锁的情况发生。
-
封锁超时:如果一个事务不能在规定的时间内获得所需的锁,就会取消该事务的请求,从而避免死锁的情况发生。在这种情况下,事务需要在等待锁的过程中,不断地检查是否超时,如果超时,则可以回滚事务,释放已经获得的锁。
并发调度的可串行化
并发调度是指在多个事务同时访问共享资源的情况下,如何安排这些事务的执行顺序,以保证系统的正确性和性能。可串行性是并发调度的一个重要准则,它指的是多个事务的并发执行结果与某一次序串行地执行这些事务时的结果相同。
举个例子,假设有两个事务T1和T2,它们要同时访问数据R1和R2。如果这两个事务的执行顺序是T1访问R1,然后T2访问R2,最后T1访问R2,T2访问R1,那么这个调度是可串行化的,因为将这两个事务按照顺序串行地执行,也会得到相同的结果。如果这个调度不是可串行化的,例如T1和T2同时访问R1和R2,那么这个调度就是不正确的,因为它可能导致数据的错误或者不一致。
要实现可串行化的调度,需要采用一些并发控制方法,例如加锁、封锁协议、事务隔离级别等。其中,加锁是最基本的并发控制方法,并且可以有效地避免并发访问的冲突。封锁协议可以保证事务的一致性和可串行性,从而避免数据的不一致和错误。事务隔离级别可以控制事务之间的相互影响,从而保证事务的独立性和正确性。
实现并发调度的可串行化
加锁:采用锁机制来控制事务之间的访问顺序和互斥访问,从而保证事务的正确性和一致性。一般来说,加锁的粒度越小,锁的争用就越少,但是也会带来更多的锁开销。
封锁协议:通过规定事务加锁的顺序,避免出现事务之间的死锁和活锁的情况,从而保证事务的可串行性和正确性。封锁协议有很多种,如二段锁、多粒度锁等。
事务隔离级别:事务隔离级别可以控制事务之间的相互影响,从而保证事务的独立性和正确性。一般来说,事务隔离级别越高,事务之间的相互影响就越小,但是也会带来更多的性能开销。
MVCC(多版本并发控制):采用多版本的方式来控制并发访问,从而保证事务的可串行性和正确性。MVCC可以避免一些常见的并发问题,例如脏读、不可重复读和幻读等