数据库中的锁用来管理对共享资源的并发访问。
lock 和 latch的区别
latch是一种轻量级的锁,对象是线程,锁的是内存数据结构。要求锁定的时间非常短。在innodb中latch又分为mutex(互斥量)和rwlock(读写锁),其目的是用来保证并发线程操作临界资源的正确性。
lock的对象是事务,用来锁定数据库中的对象,比如表,页,行。一般lock的对象在事务commit或者rollback后进行释放。
innodb中的锁
innodb中实现了两种标准的行级锁:
共享锁(S Lock):允许事务读一行数据
排它锁(X Lock):允许事务删除或者更新一行数据。
xlock与其他锁都是不兼容的,只有slock之间是兼容的。s和x
都是行锁,兼容是指对统一记录锁的兼容性情况
innodb中的表INNOD_trx,INNODB_LOCKS,INNODB_LOCKS_WAIT可以监控当前事务,并分析可能存在的锁问题。
一致性非锁定读
一致性的非锁定读指innodb存储引擎通过行多版本控制的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行delete或update操作,这是读取操作不会因此去等待行上锁的释放,而是去读取行的一个快照数据。称为非锁定读的原因是不需要等待行上面X锁的释放。该实现是通过undo实现的。在innodb存储引擎的默认设置下,这是默认的读取方式,即读取不会占用和等待表上的锁。但是在不同的事务隔离级别下,读取的方式不同,并不是在每个事务隔离级别上都采用非锁定的一致性读。
在事务隔离级别READ COMMITTED和REPEATABLE READ下(默认的事务隔离级别),采用非锁定的一致性读。在READ COMMITED事务隔离级别下,快照数据指被锁定行的最新一份快照数据。在REPEATABLE READ隔离级别下,非一致性读读取事务开始时的行数据版本。
READ COMMITTED隔离级别可能会破坏事务的隔离性,因为在一个事务过程中,对同一个记录的读取会受到其他事务提交的影响(注意要提交)。
一致性锁定读
innodb中的select操作采用一致性非锁定读,但某些情况下用户需要显示读读取加锁以保证数据逻辑的一致性。innodb对于select语句支持两种一致性的锁定读操作:
select ... for update: 对读取的行加了一个x锁,其他事务不能对读取的行加上任何锁
select ... lock in share mode:对读取的行加上了s锁,其他事务不能再加x锁。
对于一致性非锁定读,及时读取的行执行了select for update,也是可以读的(只是不能对其上锁)。需要注意的是,上述语句需要在事务中执行,因为事务提交以后锁就是否放了。
锁问题
锁会带来一些问题
脏读
脏数据:事务对缓冲池中行记录的修改,还没有提交。
脏页:是指在缓冲池中已经被修改的页,还没有刷新到磁盘中。即数据库内存中和磁盘中的数据时不一致的,当然在刷新到磁盘之前,日志都已经被写入到重做日志文件中了。
脏读就是指在不同的事务下,当前事务可以读到另外事务未提交的数据。脏读发生的前提条件是事务的隔离级别为READ UNCOMMITED。
不可重复读
不可重复读是指在一个事务内多次读取统一数据集合,在这个事务还没有结束时,另外一个事务也访问同一数据集合,并作了一些DML操作。因此在第一个事务的两次读数据之间,由于第二个事务的修改,第一个事务两次读到的数据可能是不一样的了。
脏读和不可重复度的区别是,脏读读的是未提交的数据,不可重复度读的是已经提交的数据(比如上面READ COMMITED例子)。
丢失更新
丢失更新是指一个事务的更新操作被另一个事务的更新操作覆盖,导致了数据的不一致。
其实在任何隔离级别下面,即使是READ UNCOMMITED下,对于行的DML操作,都需要对行或其他粗粒度级别的对象加锁。另一个事务会被阻塞至其他事务的提交。
从使用者的角度来说,读出数据->处理->更新回去,这中间如果并发的话,可能会造成更新的丢失(虽然不是数据库的锅),一个比较好的使用方式是读的时候用select ... for update.
死锁
死锁是指两个或以上事务再执行过程中,因为争夺资源造成的一种互相等待的现象。解决死锁问题最简单的方式是不要有等待,将所有的等待转化为回滚,并且事务重洗能开始。
解决死锁的另一种方式是超时,超时进行回滚。通过innodb_lock_wait_timeout设置超时时间。(如果占用很多undo的事务超时,代价比较高)
处理超时,当前数据库普遍采用wait-ffor graph的方式进行死锁检测。需要数据库保存锁的信息链表和事务的等待链表。通过这个可以构造出一张图,如果存在回路就存在死锁。这是一种主动检测死锁的机制,每个事务请求所并发生等待的时候都会判断是否存在回路,如果存在则有死锁。通常来说innodb选择undo量最小的事务回滚。
innodb判断出死锁的时候会报错,并自动回滚。
锁升级
锁升级是指将当前锁的粒度降低。因为锁是一种稀有资源,所以要避免锁的开销(防止系统使用太多的内存来维护锁)。 ->将锁的范围变大,带来并发性能的降低
innodb不是根据每个记录来产生行锁,而是根据每个事务访问的每个页对锁进行管理,采用的是位图的复方石,不管一个事物锁住页中的一个记录还是多个记录,开销都是一致的。