乐观锁:

它假设多用户并发的事务在处理时不会彼此互相影响,各事务能够在不产生锁的情况下处理各自的那部分数据。

在提交数据更新之前,每个事务会先检查在该事务读取数据后,有没有其他事务又修改了该数据。如果其他事务有更新的话,正在提交的事务会进行回滚。

数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据,不要了,从新从数据库中取,之后再尝试更新

实现数据版本有两种方式,第一种是使用版本号,第二种是使用时间戳 使用版本号实现乐观锁

使用版本号时,可以在数据初始化时指定一个版本号,每次对数据的更新操作都对版本号执行+1操作。并判断当前版本号是不是该数据的最新的版本号。

优点与不足

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都同时读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

悲观锁:

悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。如果一个事务读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。

Mysql用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。这些锁统称为悲观锁。

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。 

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。 

MyISAM表锁

MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。 

对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;

对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;

******************************************************************************************

如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作 (UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,给MyISAM表显示加锁,一般是为了在一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。

Lock tables orders read local, order_detail read local; Select sum(total) from orders; Select sum(subtotal) from order_detail; Unlock tables;

给表加锁时,必须同时取得所有涉及到表的锁,并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;MyISAM表不会出现死锁(Deadlock Free)的原因。

并发插入(Concurrent Inserts) 

MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

  • concurrent_insert设置为0时,不允许并发插入。
  • concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
  • concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

可以利用MyISAM存储引擎的并发插入特性,来解决应 用中对同一表查询和插入的锁争用

例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行 OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。

MyISAM的锁调度

默认情况下MyIsam的写锁优先级要高于读锁,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前。

可以通过一些参数来设置MyIsam的调度行为:

  • SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。

通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先

***********************************************************************************************************

InnoDB锁

InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION);二是采用了行级锁。

事务:

为什么要使用事务?

相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持可以支持更多的用户。

  • Actomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
    • Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以操持完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
    • Isolation):数据库系统提供一定          的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
    • Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

并发事务带来的问题 

  • Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了其他事务所做的更新。
  • Dirty Reads):一个事务正在对一条记录做修改,在这个事务并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”的数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做“脏读”。
  • Non-Repeatable Reads):一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫做“不可重复读”。
  • Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

不可重复读和脏读的区别:脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

幻读和不可重复读的异同:都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

事务隔离级别 

●读未提交(Read Uncommitted)

含义解释:只限制同一数据写事务禁止其他写事务。解决”更新丢失”。(一事务写时禁止其他事务写

名称解释:可读取未提交数据

所需的锁:排他锁

●读已提交(Read Committed)

含义解释:只限制同一数据写事务禁止其它读写事务。解决”脏读”,以及”更新丢失”。(一事务写时禁止其他事务读写

名称解释:必须提交以后的数据才能被读取

所需的锁:排他写锁、瞬间共享读锁

●可重复读(Repeatable Read)

含义解释:限制同一数据写事务禁止其他读写事务,读事务禁止其它写事务(允许读)。解决”不可重复读”,以及”更新丢失”和”脏读”。(一事务写时禁止其他事务读写、一事务读时禁止其他事务写)

注意没有解决幻读,解决幻读的方法是增加范围锁(range lock)或者表锁。

名称解释:能够重复读取

所需的锁:排他写锁、共享读锁

●串行化(Serializable)

提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。(一事务写时禁止其他事务读写、一事务读时禁止其他事务读写

限制所有读写事务都必须串行化实行。

****************************************************************************************************

除了加锁机制也可以采用多版本并发控制(MVCC)MVCC主要是为Repeatable-Read事务隔离级别做的

在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。

当前读:读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事物不会再并发修改这条记录(更新,删除操作属于当前读,需要加锁)

快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。(select操作,属于快照读,不加锁)

select * from table where ?;

InnoDB会根据以下两个条件检查每行记录:

a.InnoDB只会查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.

b.行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除.

只有a,b同时满足的记录,才能返回作为查询结果.

快照读原理:

会在原数据的基础上追加--->事务id,回滚指针,删除id。

事务id:每执行一次事务,事务id会自动+1

回滚指针:事务操作失败,通过回滚指针指向的undo日志记录进行回滚

删除id:用来记录该记录是否被删除,这里不是真正的删除,而只是标志出来,真正的删除在事务提交的时候。

具体执行流程

begin->用排他锁锁定该行->记录redo log->记录undo log->修改当前行的值,写事务编号,回滚指针指向undo log中的修改前的行。

******************************************************************************************************

InnoDB的行锁模式及加锁方法

共享锁(s):又称读锁。允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁

In share mode

若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

排他锁(X):又称写锁。允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。

若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。

【注意】排它锁是其他事务不能再对已有排它锁的数据加其他的锁,但是普通查询没有任何加锁,所以可以用select进行对数据的查询。

为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁.


意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会加任何锁。 如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。 

事务可以通过以下语句显式给记录集加共享锁或排他锁:

  • S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
  • X):SELECT * FROM table_name WHERE ... FOR UPDATE

************************************************************************************************

InnoDB行锁实现方式

InnoDB行锁是通过给索引上的索引项加锁来实现的,也就是意味着只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁

(1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁。

(2)MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突.

(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。 

(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决 定的,如果MySQL认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。

间隙锁(Next-Key锁)

用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的 索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁 (Next-Key锁)。 

目的是为了防止幻读。使用范围条件检索并锁定记录时,InnoDB这种加锁机制会阻塞符合条件范围内键值的并发插入,这往往会造成严重的锁等待。因此,在实际应用开发中,尤其是并发插入比较多的应用,我们要尽量优化业务逻辑,尽量使用相等条件来访问更新数据,避免使用范围条件。

 

小结

(1)共享读锁(S)之间是兼容的,但共享读锁(S)与排他写锁(X)之间,以及排他写锁(X)之间是互斥的,也就是说读和写是串行的。 

(2)在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表查询和插入的锁争用问题。 

(3)MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。 

(4)由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

对于InnoDB表,本文主要讨论了以下几项内容: 

(1)InnoDB的行锁是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。 

(2)介绍了InnoDB间隙锁(Next-key)机制,以及InnoDB使用间隙锁的原因。 

在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。

在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:

  • 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会;
  • 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁;
阅读更多
换一批

没有更多推荐了,返回首页