第六章-锁

一方面要最大程度地利用数据库的并发访问,另外一方面还要确保每个用户能以一致的方式读取和修改数据,因此出现了锁(locking)机制。

6.1 什么是锁 249

锁是数据库系统区别于文件系统的一个关键特性。数据库系统使用锁是为了支持对共享资源(注意是共享资源,不单单指行记录)进行并发访问,提供数据的完整性和一致性。
有多少种数据库,就可能有多少种锁的实现方法。用户可能对某个特定的关系数据库系统的锁定模型有一定的经验,但是不一定知道其他数据库的。

6.2 lock与latch 250

latch一般称为闩锁(轻量级的锁),因为其要求锁定的时间必须非常短。若持续的
时间长,则应用的性能会非常差。在InnoDB存储引擎中,latch 又可以分为mutex (互斥量)和rwlock(读写锁)。

lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。并且一般lock
的对象仅在事务commit或rollback后进行释放且是有死锁机制的。

下表显示它们的不同:

6.3 InnoDB存储引擎中的锁 252

6.3.1 锁的类型 252

InnoDB存储引擎实现了如下两种标准的行级锁:

  • 共享锁(S Lock),允许事务读一行数据。
  • 排他锁(X Lock),允许事务删除或更新一行数据。

为了支持在不同粒度上进行加锁操作,InnoDB 存储引擎支持一种额外的锁方式,称之为意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度(fine granularity).上进行加锁, 如图6-3所示。
若将上锁的对象看成一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象
进行上锁,那么首先需要对粗粒度的对象上锁。例如图6-3,如果需要对页上的记录r进行上X锁,那么分别需要对数据库A、表、页上意向锁IX,最后对记录r上X锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。

InnoDB存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁。设计目的主
要是为了在一个事务中揭示下一行将被请求的锁类型。其支持两种意向锁:
1)意向共享锁(IS Lock), 事务想要获得一张表中某几行的共享锁
2)意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁

6.3.2 一致性非锁定读 258

一致性的非锁定读( consistent nonlocking read)是指InnoDB存储引擎通过行多版
本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的放。相反地,InnoDB 存储引擎会去读取行的一个快照数据。

快照数据:指该行的之前版本的数据。

不同的事务隔离级别下,读取快照数据的方式不同:

  • READ COMMITTED事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。

  • REPEATABLEREAD事务隔离级别下,非一致性读总是读取事务开始时的行数据版本(就是这个事务开始时的数据,只要这个事务没有结束,则读取到的数据一直是这个版本的数据)

6.3.3 一致性锁定读 261

InnoDB存储引擎的SELECT操作使用一致性非锁定读。但是在某些情况下,用户需要显式地对数据库读取操作进行加锁以保证数据逻辑的一致性。

InnoDB存储引擎对于SELECT语句支持两种一致性的锁定读( locking read)操作:

  • 口SELECT…FOR UPDATE
  • SELECT…LOCK IN SHARE MODE

SELECT…FOR UPDATE对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁。SELECT…LOCK IN SHARE MODE对读取的行记录加一个S锁,其他事务可以向被锁定的行加S锁,但是如果加X锁,则会被阻塞。

6.3.4 自增长与锁 262

自增长的分类

从MySQL 5.1.22版本开始,InnoDB 存储引擎中提供了一种轻量级互斥量的自增长
实现机制,这种机制大大提高了自增长值插人的性能。并且从该版本开始,InnoDB 存储引擎提供了一个参数innodb_ autoinc lock_ mode 来控制自增长的模式,该参数的默认值为1.

注意:

  • AUTO-INC Locking这种锁采用一种特殊的表锁机制,为了提高插人的性能,锁不是在一个事务完成后才释放,而是在完成对自增长值插人的SQL语句后立即释放(存在一些性能问题)。
  • 另外,在InnoDB存储引擎中,自增长值的列必须是索引,同时必须是索引的第一个列。如果不是第一个列, 则MySQL数据库会抛出异常,而MyISAM存储引擎没有这个问题。

6.3.5 外键和锁 264

在InnoDB存储引擎中,对于一个外键列,如果没有显式地对这个列加索引,InnoDB 存储引擎自动对其加一个索引。

6.4 锁的算法 265

6.4.1 行锁的3种算法 265

  • Record Lock:单个行记录上的锁
  • Gap Lock:间隙锁,锁定-一个范围,但不包含记录本身
  • Next-Key Lock : Gap Lock+Record Lock,锁定一个范围, 并且锁定记录本身

当查询的索引含有唯一属性时,InnoDB 存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock,即仅锁住索引本身,而不是范围。

6.4.2 解决Phantom Problem 269

Phantom Problem(幻读)是指在同一事务下,连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能会返回之前不存在的行。

在默认的事务隔离级别下,即REPEATABLEREAD下,InnoDB存储引擎采用
Next- Key Locking机制来避免Phantom Problem (幻像问题)。

6.5 锁问题 271

锁提高了并发,但是却会带来潜在的问题。不过好在因为事务隔离性的要求,锁只会带来三种问题。

6.5.1 脏读

脏读违反了数据库的隔离性

脏页指的是在缓冲池中已经被修改的页,但是还没有刷新到磁盘中的页。
脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交(commit)。
脏读指的就是在不同的事务下,当前事务可以读到另外事务未提交的数据,简单来
说就是可以读到脏数据。

6.5.2 不可重复读 273

不可重复读违反了数据库事务一致性

不可重复读是指在一个事务内多次读取同一数据集合。在这个事务还没有结束时,
另外一个事务也访问该同一数据集合,并做了一些DML操作。因此,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的情况。

不可重复读和脏读的区别是:脏读是读到未提交的数据,而不可重复读读到的却是
已经提交的数据。

一般来说,不可重复读的问题是可以接受的,因为其读到的是已经提交的数据,本
身并不会带来很大的问题。

因此,InnoDB 存储弓|擎的默认事务隔离级别是READ REPEATABLE,采用Next-Key Lock算法,避免了不可重复读的现象。

6.5.3 丢失更新 274

丢失更新是另一个锁导致的问题,简单来说其就是一个事务的更新操作会被另一个
事务的更新操作所覆盖,从而导致数据的不一致。

在当前数据库的任何隔离级别下,都不会导致数据库理论意义上的丢失更新问题。

但是在生产应用中会常出现下面的情况:

1)事务T1查询一行数据,放人本地内存,并显示给一个终端用户User1。
2)事务T2也查询该行数据,并将取得的数据显示给终端用户User2。
3) User1 修改这行记录,更新数据库并提交。
4) User2修改这行记录,更新数据库并提交。

这个过程中用户Userl的修改更新操作“丢失”了!

解决方法:要避免丟失更新发生,需要让事务在这种情况下的操作变成串行化,而不是并行的操作——上锁。

6.6 阻塞 276

因为不同锁之间的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中
的锁释放它所占用的资源,这就是阻塞。阻塞不是坏事,是为了确保事务可以并发且正常的运行。

在InnoDB存储引擎中,可以对阻塞等待的时间( 默认是50秒)和是否在等待超时时对进行中的事务进行回滚操作(默认不回滚)进行设置。

6.7 死锁 278

6.7.1 死锁的概念 278

死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待
的现象。若无外力作用,事务都将无法推进下去。

因此,除了超时机制(死锁超时,回滚其中一个事务的操作),当前数据库还都普遍采用wait-for graph ( 等待图)的方式来进行死锁检测。

wait-for graph采用深度优先的算法实现(非递归)

wait-for graph要求数据库保存以下两种信息:

  • 锁的信息链表
  • 事务等待链表

如果图中存在回路,则代表存在死锁,InnoDB存储引擎选择回滚undo量最小的事务。

如下图

上图形成的wait-for graph如下图:

6.7.2 死锁概率 280

死锁发现的概率很小

6.7.3 死锁的示例 281

  • AB-BA死锁
  • 此外还存在另一种死锁,即当前事务持有了待插入记录的下一个记录的X锁,但是在等待队列中存在一个 S锁的请求,则可能会发生死锁。

6.8 锁升级 283

锁升级(Lock Escalation) 是指将当前锁的粒度降低。举例来说,数据库可以把个表的1000个行锁升级为一个页锁,或者将页锁升级为表锁。

InnoDB存储引擎不存在锁升级的问题。因为其不是根据每个记录来产生行锁的,相
反,其根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。因此不管一个事务锁住页中一个记录还是多个记录,其开销通常都是一致的。

6.9 小结 284

虽然每个数据库在SQL语句层面上的差别可能不是很大,在内部底层的实现却各有不同。通过理解InnoDB存储引擎锁的特性,对于开发一个高性能、高并发的数据库应用显得十分重要和有帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值