锁是计算机协调多个进程或春线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU,RAM,I/O)的争用之外,数据也是一种工许多用户共享的资源。如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问的性能的一个重要因素。从这个角度来说,锁堆数据库而言显得尤其重要,也更加复杂。
概述
相对其他数据库而言,MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。MySQL大致可以归纳为以下2种锁:
表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发最低。
行级锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率最小,并发度也最高。
页面锁:开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁之间。并发度一般。
MySQL表级锁的锁模式(MyISAM)
MySQL表级锁有两种模式:读锁(Table Read Lock)和写锁(Table Write Lock)。
对MyISAM读操作,不会阻塞其他用户对同一表请求,但是会阻塞对同一表的写请求;
对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作。
MyISAM读操作和写操作之间,以及写操作之间是串行的。
当一个线程获得对一个表的写操作后,只有持有所线程可以对表进行更新操作,其他线程的读,写操作都会等待,直到锁被释放。
如何加表锁:
MyISAM在执行查询语句(SELECT)前,会自动给设计的所有表加读锁,在执行更新操作(UPDATE, DELETE,INSERT)前,会自动给涉及的表加写锁。这个过程并不需要用户干预,因此用户一般不需要直接用户LOCK TABLE命令给MyISAM表显示加锁。
在用LOCK Tables给表显示加锁时,必须同时取得所有涉及表的锁,并且MySQL支持锁升级。也就是说,在执行LOCK TABLE后,只能访问显示加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也是基本如此,MySQL问题一次获得SQL语句所需要的全部锁。这也是MyISAM表不会出现死锁的原因。
并发锁:
在一定条件下,MyISAM也支持查询和操作的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门泳衣控制其并发插入的行为,其值分别为0,1或者2.
当concurrent_insert设置为0时,不允许并发插入。
当concurrent_insert设置为1时,如果MyISAM允许在一个读表的同时,另一个进程从表尾插入记录,这也是MyISAM的默认设置。
当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾插入记录,都允许在表尾并发插入记录。
可以咯用MyISAM存储引擎插入特性,来解决应用中对同一表查询和插入锁争用。例如,将concurrent_insert系统变量为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIONMIZE TABLE语句来整理空间碎片。受到因删除记录而产生的中间空洞。
MyISAM的锁调度
前面讲过,MyISAM存储引擎的读和写错做是互斥的,读操作是串行的。那么,一个进程请求某个MyISAM表的读锁,同时另一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先获得锁。不仅如此,即使读进程先到达所等待队列,写请求猴岛,写锁也会插入到读请求之前。这是因为MySQL认为写请求一般比读请求重要。这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因。因为大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况又是可能会变得非常糟糕。型号我们可以通过一些设置来调节MyISAM的调度行为。
通过制定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以有限的权利。
通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
通过制定INSERT,UPDATE,DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
虽然上面3中方法都是要么更新优先,要么查询优先的方法,但是还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重放问题。
另外,MySQL也提供了一种这种的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个标的读锁达到这个值后,MySQL暂时将写请求的优先级降低,给读进程一定获得锁的机会。
上面已经讨论了写优先调度机制和解决办法,这里还要强调一点:一些需要长时间运行的查询操作,也会使写进程“饿死”。因此,应用中应尽量避免出现长时间运行放查询操作。不要总想用一条SELECT语句来解决问题。因为这种看似巧妙的SQL语句,往往比较复杂,执行时间比较长,在可能的情况下,可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成。从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲的时段执行。
InnoDB锁问题
InnoDB与MyISAM的最大不同有两点:一是支持事物(TRANSACTION);二是采用了行级锁。
行级锁和表级锁本来就有许多不同之处,另外,事物的引入也带来了一些新的问题。
1.事物(Transaction)及其ACID属性
事物是有一组SQL语句组成的逻辑处理单元,事物具有4属性,通常称为ACID属性。
原子性(Actomicity):事物是一个原子操作,其对数据的修改,要么全部执行,要么全都不执行。
一致性(Consistent):在事物开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事物的修改,以保持完整性;事物结束时,所有的内部数据结构(如B树索引和双向链表)也都必须是正确的。
隔离性(Isolation):数据库系统提供一定的隔离机制,保证事物在不受外部并发操作影响的“独立”环境执行。这意味着事物处理过程中的中间状态对外部是不可见的。
持久性(Durable):事物完成之后,它对数据库的修改时永久的,即使出现系统故障也能够保持。
2.并发事物带来的问题
相对于穿行处理莱索,并发事物处理能大大增加数据库的利用率,提高数据库的事物吞吐量,从而可以支持更多的用户。但是并发事物处理也会带来一些问题。主要包括以下几种。
更新丢失(Lost Update):当两个或者多个事物选择同一行,然后给予最初选定的值更新该行时,由于每个事物都不知道其他事物的存在,就会发生丢失更新问题,最后的更新覆盖了其他事物锁做的更新。例如,两个编辑人员制作统一文档的电子副本。每个编辑人员独立的更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的修改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件。则可避免此问题。
脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务提交之前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据以来关系。这种现象被形象的叫做“脏读”。
不可重复读(Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变,或某系记录已经被删除了,这种现象叫做“不可重复读”。
幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事物插入了满足其查询条件的新数据,这种现象就称为“幻读”。
事物的隔离级别
在并发事物处理带来的问题中,“更新丢失”通常应该是完全避免的,但是防止更新丢失,并不能但依靠数据库事物控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失时应用的责任。
“脏读”,“不可重复读”和“幻读”都是数据库一致性问题,必须由数据库提供一定的事物隔离机制来解决,数据库实现事物隔离的方式,基本可以分为以下两种。
一种是在读取数据前,对其加锁,组织其他市区对数据进行修改。
另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别的一致性读取。从用户的角度,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC)也经常称为多版本数据库。
事物4种隔离级别比较
隔离级别 | 数据一致性 | 脏读 | 不可重复读 | 幻读 |
未提交读(Read uncommitted) | 最低级别,只能保证不读取物理上损坏的数据 | 是 | 是 | 是 |
已提交读(Read committed) | 语句级 | 否 | 是 | 是 |
可重复读(Repeatable read) | 事务级 | 否 | 否 | 是 |
可序列化(Serializable) | 最高级别,事务级 | 否 | 否 | 否 |
InnoDB的行锁模式和加锁方法
InnoDB实现了以下两种类型的行锁:
共享锁(s):允许一个事务去读一行,阻止其他事物获得相同数据集的排他锁。
排他锁(X):允许获取排他锁的事物更新数据,阻止其他事物获得相同的数据共享读锁和排他写锁。
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用放意象锁,这两种意象锁都是表锁。
意向共享锁(IS):事物打算给数据行共享锁,事物在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事物打算给数据行加排他锁,事物在给一个数据行家排他锁之前必须先取得该表的IX锁。