数据库作为多用户共享资源,同一时刻会有很多的用户来访问这一公共资源。当多个用户并发访问的时候,数据库需要合理的控制资源的访问规则,来保证数据读写的正确性。而锁就是用来实现这些访问规则的重要手段。在数据库中存在各种各样的锁。以mysql为例,数据库中的锁,大概有如下几种:根据数据操作的方式,数据库可以分为读锁和写锁。
写锁:
当一个线程或者一个事务,要对某个“资源”进行写操作时,首先需要获取对这个“资源”的写权限,为了防止这一时刻其他线程同时进行写操作,而产生数据写入错乱的情况,先获取到写权限的线程会给对应的“资源”添加写锁,写锁之间是互斥的,这个互斥的特性,保证了同一时间,只可能有一个线程获取这这个锁。其他想要更新这个“资源”的线程都会被锁住,直到拥有该“资源”该线程释放了这个写锁。换句话说,也就是每个线程只能按照获取锁的顺序一个一个的写,从而避免了并发场景下出现数据不正确的问题。
写锁决定了在并发情况下写入到数据库中数据的正确性,防止并发写的过程中产生数据写入错乱等不可预知的情况
读锁:
当一个线程,要对某个“资源”进行读操作时,会获取对这个“资源”的读权限,因为读操作不涉及到“资源”的修改,多个读操作之间是可以并行,也就是说读锁之间不是互斥的。读写锁之间是互斥的,主要为了防止读取到写入的中间结果。
根据锁的作用范围:可以分为全局锁,表锁,行锁。
全局锁
是对整个数据库实例加锁,使用方式如下:Flush tables with read lock (FTWRL)。主要就是用来在某一段时间内锁住这个数据库实例,防止数据方式变动,便于数据的备份。
不过使用全局锁会有以下不足:
1.如果对从库添加了全局读锁,在加锁期间,不能从主库同步binlog,导致主从延迟。
2.如果对主库添加了全局读锁,在加锁期间,数据库无法响应任何数据更新。
为了完成完整的数据备份的需求,还有其他更多优秀的方案:在支持事务的Innodb中,使用事务就可以完成,在重复读隔离级别中,在事务开启后,到事务提交前的时间过程中,看到的视图都是一致,实现完整的数据备份。因为全局锁,影响范围较大,使用的场景较少。
表锁
按照锁的资源不同表锁可以分为:锁数据的表锁和锁元数据的元数据锁。
锁数据的表锁:粒度是表级别的,使用表锁的命令:lock tables t1 read,t2 write.此命令表示:对表t1加读锁,对t2添加写锁。执行此命令后,其他线程对t1表只能读,不能写,对t2表即不可读也不可写。而执行lock命令的线程可以对t1表进行读,不能写,因为此时其他线程可以对t1进行读,如果当前线程进行写的话,会导致其他线程读到中间结果。执行lock命令的线程,对t2表可读可写,因为其他线程对t2表不可读也不可写,当前线程对t2表进行读写,也不会产生冲突错误。
锁元数据的表锁(metadata lock,MDL):MDL的作用是保证数据读写的正确性,保障在对数据进行读写的过程中,不能对表结构进行修改,MDL不需要显示使用,在访问一个表的时候,会被自动打开,当访问表的事务结束后,MDL会被释放(在mysql5.6后支持了onlineddl,解除了这一限制)。
行锁
粒度相对于表锁更小的锁,每次只会影响一行或者多行,防止多个线程同时更新同一目标数据行。在innodb中一个事务可以获取多个行锁,只有当整个事务提交之后,该事务中持有的行锁才会被释放。如果在事务执行过程中,有其他事务也需要某一个行锁,那么后者就会处于阻塞状态,只有等前者事务提交后,后者才会从阻塞状态唤醒并获取到锁或者等待超时,返回空的数据集。
行锁,在需要时(对某个行进行修改操作)会自动获取,当修改语句执行完毕却并不会立即释放,只有当这条语句所在事务提交之后才会释放整个锁。所以依照行锁的这种机制,在一个事务中,有多个操作时,要将可能会获取行锁的更新语句在不影响业务的前提下,放在整个事务中所有操作的最后面,这样可以使其他事务阻塞时,阻塞的时间更短。
还有经常被提到的悲观锁和乐观锁,这两个虽然名字里带了锁,但是并不是真正意义上的锁,这两个“锁”是用来解决并发问题的一种通用思想或者解决方案。只不过在实现的过程中可能会用到锁而已。