【锁住你的知识点】讨论一下数据库读/写锁的细节

303 阅读3分钟

Locking

谈到并发系统,不得不提锁。数据库系统一般有两种锁。

  1. shared lock: 写锁
  2. exclusive lock: 读锁。

下面是锁的特性

  • read lock和 write lock不可以同时被获得。
  • 多个进程可以对一个对象加写锁.
  • 只有一个进程可以获得写锁。

很简单对吧,但是我们还要更深入一点,这是我们的风格,让我们显得更加专业。所以下面我们具体看一看算法的实现吧。

read lock

read_lock(X)
B: if LOCK(X) == 'unlocked'
			then begin LOCK(X) <- "read-locked";
					 num_of_reads(X) <- 1
					 end
   else if(LOCK) == "read-locked"
			then num_of_reads(X) <- num_of_reads(X) + 1;
	 else begin
			wait(until LOCK(X) == "unlocked"
           and the lock manager wakes up the transaction);
			go to B;
		end;

write lock

write_lock(X):
B: if LOCK(X) == "unlocked"
			then LOCK(X) <- "write-locked"
   else begin
			wait(until LOCK(X) == "unlocked" and 
						the lock manager wakes up the transaction);
			go to B;
	 end;
unlock(X)
	if LOCK(X) == "write-locked"
			then begin LOCK(X) <- "unlocked";
					 wake up one of the waiting transactions, if any;
					 end;
	else if LOCK(X) == "read-locked"
			     then begin
							num_of_reads(X) <- num_of_reads(X) -1;
							if num_of_reads(X) ==0
									then begin LOCK(x) == "unlocked";
											 wake up one of the waiting transactions, if any;
											 end;
							end;
  end;

Naive Lock

下面讨论的是具体怎么应用锁。最天真的做法是读写之前获得锁,之后立刻释放。

就上边的情况假设X=20, Y=30。我们分三种执行顺序分析

  1. 顺序执行 T1, T2 结果:x=50, y = 80

  2. 顺序执行 T2, T1 结果:x=70, y=50

  3. 交叉执行

结果: x=y=50

根据执行的隔离性,显然1,2两种情况是可以接受的(第三种情况不是按顺序执行的),而第三种情况应该杜绝。

怎么解决呢?

Two Phase locking protocol

为了避免上述的错误,我们可以通过让所有获得锁的语句在任何释放锁语句之前。

好了问题解决了,但是又引入另一个问题。死锁。。。比如下面的执行顺序

Dead Lock

死锁怎么构成的?我们可以简单概括为进程之间互相等待。比如老板等员工干完活发工资。员工等老板发工资后再干活。这就是一个无尽的等待。一般情况下,我们可以使用下面几种方法解决。

  1. Conservative 2PL. 任何一个事务必须在开始之前声明要获得的所有锁,并且同时要获得它们。注意这是原子性的,即获得所有锁必须一次性完成,不被其他进程干扰。
    1. 这种方法简单容易让人理解,但是不够有效率
  2. TIME OUT: 这是主流的方法。如果一个事务在规定时间内没有完成,那么就要终止这个事务。
  3. dead-lock detection: 这个比较费时间,需要画一个waiting-for图表。如果发现图标中有互相等待现象,就终止一个事务。

像这幅图所示,T1, T2, T3互相等待,随便终止一个,比如T1,那么死锁就被解决了。

总结

今天我们讨论了一下死锁,分析了一些场景,希望你对数据库的理解更加深刻。我之前认为技术就是会用就行,可是现在发现会用有时候不能解决问题,比如我会写SQL,但是我写的和别人的相比,效率却可能差很远,这时候才意识到,其实更应该看本质。

比如之前写socket变成,学了很多API,但是总结了TCP/UDP之后,发现API就算不学,我也能猜出来。这就是学习本质带来的好处。学习本质不费时间,费时间的是学习太多语法糖。