夏有清风

最是清風明月知人意,涼爽了整個如詩的夏夜

Open Source, Open Mind,
Open Sight, Open Future!
  menu
7 文章
12547 浏览
1 当前访客
ღゝ◡╹)ノ❤️

锁、索引和事务

锁、索引和事务

事务

前几天去面试,二面的面试官是公司上海区的研发经理,虽然稍微提前准备温习了一些数据库方面的知识,但还是被虐的体无完肤。 抛开一些顺带问的技术问题,整个面试过程其实是围绕着事务进行的。虽然知道事务是什么意思,但当需要对其进行专业的定义时,一时间竟不知道该如何回答。

事务就是要保证一段代码的一致性,即要么全部成功,要么全部失败;以及失败后的回滚措施。

当我给出上述回答的时候,感觉面试官并没有特别的满意。事后仔细想了一下,我只解释了事务的原子性(Atomicity),并没有详细讲述一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。ACID应该是每个了解MySQL的程序员都知道的名词,但是面试的时候却忘得一干二净,着实有点难堪了。

Spring事务

参考资料1:Spring五个事务隔离级别和七个事务传播行为
参考资料2:可能是最漂亮的Spring事务管理详解

事务的隔离级别

事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。

隔离级别含义
DEFAULT使用后端数据库默认的隔离级别
READ_UNCOMMITTED最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
READ_COMMITTED允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
REPEATABLE_READ对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
SERIALIZABLE最高的隔离级别,完全服从ACID的隔离级别

事务的传播行为

事务传播行为指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

传播行为含义
REQUIRED表示当前方法必须运行在事务中,如果上下文含有事务,则加入该事务执行,否则启动一个新事务
SUPPORTS表示当前方法不需要事务,如果上下文含有事务,则加入该事务中执行
MANDATORY表示当前方法必须运行在事务中,如果事务不存在,则抛出异常
REQUIRES_NEW表示当前方法必须运行在自己的事务中,如果调用时上下文存在事务,则该事务会被挂起
NOT_SUPPORTED表示当前方法不应该运行在事务中,如果调用时上下文存在事务,则该事务会被挂起
NEVER表示当前方法不应该运行在事务中,如果调用时上下文存在事务,则抛出异常
NESTED如果上下文存在事务,则会开启一个嵌套事务

分布式事务

当最后面试官问道分布式事务时,我不知道该如何回答。强调自己做的服务都是无状态的,最后其实也没有回答上来到底什么是无状态

参考资料:分布式事务的基础

CAP定理

  • C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据上来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。

  • A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应指的是系统应该明确返回结果并且结果是正确的,这里的正确指的是比如应该返回50,而不是返回40。

  • P (分区容错性):当出现网络分区后,系统能够继续工作。打个比方,这里个集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正常工作。

在CAP定理中,三者不能共有。在分布式系统中,网络无法100%可靠,分区其实是一个必然现象,如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。

BASE

BASE 是 Basically Available、Soft state和 Eventually consistent三个短语的缩写。是对CAP中AP的一个扩展。

  1. 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
  2. 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
  3. 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。

BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。

对于分布式事务的解决方案,资料实在是太多,我觉得可以以后专门做一次学习归纳。

锁和索引

之后面试官抛出了一个新的问题“数据库锁和索引有什么关系?”。

我听到问题的时候都懵了。在印象中锁就是锁,锁是为了并发;索引是为了提高查询速度。后来查了下,锁和索引的关系大概指的是数据库锁锁住的是索引吧,但我感觉有些牵强。(大概这也是我个人水平低的原因吧)

共享锁和排它锁

1. 共享锁(Shared lock)

Shared locks support read integrity. They ensure that a record is not in the process of being updated during a read-only request. Shared locks can also be used to prevent updates of a record between the time that a record is read and the next syncpoint.

在IBM的Knowledge Center中,共享锁的定义是确保在只读请求期间保证记录不会被更新,也就是说,当对某个资源添加共享锁后,其他线程可以继续添加S锁,也就是所谓的共享锁兼容,表示共享锁不会阻止其它session同时读资源。但是其他线程无法再添加排它锁,因为对同一资源无法同时存在共享锁及排它锁。

共享锁死锁
T1:
begin tran
select * from table (holdlock)
update table set column1='hello'

T2:
begin tran
select * from table(holdlock)
update table set column1='world'
  • 线程T1执行select语句添加共享锁,T2执行select语句添加共享锁。
  • T1执行完select操作后,执行update操作,需要将共享锁升级为排它锁,但是T2还持有共享锁,所以T1进入锁等待。T2同样在将共享锁升级为排它锁时,因为T1持有共享锁而进入锁等待。
  • 死锁产生。

为了避免死锁的情况发生,我们可以在select操作时可以通过select … for update直接添加排它锁。面试时面试官问我select操作是否都是共享锁,我没回答出来..。但是这样会导致后续的只读操作进入等待,在并发量很高的情况下,十分影响性能。

2. 排它锁(Exclusive lock)

Exclusive locks protect updates to file resources, both recoverable and non-recoverable. They can be owned by only one transaction at a time. Any transaction that requires an exclusive lock must wait if another task currently owns an exclusive lock or a shared lock against the requested resource.

同样根据IBM的解释,排它锁的定义是保护资源在同一时间只会有一个事务(任务)对其进行更新。

行锁和表锁

面试时,面试官问了我行锁和表锁的区别,我大概只记得走索引的查询加的是行锁,别的操作会添加表锁。事后查阅了一下,似乎回答的并不全面。首先SQL是否真正地走了创建的索引,其次行锁升级为表锁的问题也没有考虑到。

行锁
  • 行锁的劣势:开销大;加锁慢;会出现死锁
  • 行锁的优势:锁的粒度小,发生锁冲突的概率低;处理并发的能力强
  • 加锁的方式:自动加锁。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁;对于普通SELECT语句,InnoDB不会加任何锁,也可以显示的加锁
行锁优化
  1. 尽可能让所有数据检索都通过索引来完成,避免无索引行或索引失效导致行锁升级为表锁。
  2. 尽可能避免间隙锁带来的性能下降,减少或使用合理的检索范围。
  3. 尽可能减少事务的粒度,比如控制事务大小,而从减少锁定资源量和时间长度,从而减少锁的竞争等,提供性能。
  4. 尽可能低级别事务隔离,隔离级别越高,并发的处理能力越低。
表锁
  • 表锁的优势:开销小;加锁快;无死锁
  • 表锁的劣势:锁粒度大,发生锁冲突的概率高,并发处理能力低
  • 加锁的方式:自动加锁。查询操作(SELECT),会自动给涉及的所有表加读锁,更新操作(UPDATE、DELETE、INSERT),会自动给涉及的表加写锁
总结
  1. InnoDB 支持表锁和行锁,使用索引作为检索条件修改数据时采用行锁,否则采用表锁
  2. InnoDB 自动给修改操作加锁,给查询操作不自动加锁
  3. 行锁可能因为未使用索引而升级为表锁,所以除了检查索引是否创建的同时,也需要通过explain执行计划查询索引是否被实际使用
  4. 行锁相对于表锁来说,优势在于高并发场景下表现更突出,毕竟锁的粒度小
  5. 当表的大部分数据需要被修改,或者是多表复杂关联查询时,建议使用表锁优于行锁
  6. 为了保证数据的一致完整性,任何一个数据库都存在锁定机制。锁定机制的优劣直接影响到一个数据库的并发处理能力和性能

记录锁和间隙锁

在问数据库锁的问题时,面试官有顺带问我是否了解锁的算法。当时只能表示并没有了解过这方面的知识。

记录锁

记录锁是锁住记录的,锁住的是索引记录,而不是我们真正的数据记录。

  • 如果锁的是非主键索引,会在自己的索引上面加锁之后然后再去主键上面加锁锁住。
  • 如果表上没有索引(包括没有主键),则会使用隐藏的主键索引进行加锁。
  • 如果要锁的没有索引,则会进行全表记录加锁。
间隙锁

间隙锁会锁定某一个范围,不会阻塞其他的间隙锁。但是会阻塞插入间隙锁,这也是用来防止幻读的关键。

next-key 锁

这个锁本质是记录锁加上 gap 锁。在 RR 隔离级别下(InnoDB 默认),InnoDB 对于行的扫描锁定都是使用此算法,但是如果查询扫描中有唯一索引会退化成只使用记录锁。

因为唯一索引能确定行数,而其他索引不能确定行数,需要使用间隙锁防止其他事务中再次添加这个索引的数据造成幻读。RR 隔离级别下,InnoDB 使用 Next-Key Lock 算法避免了幻读。

插入意向锁

插入意向锁是在插入的时候产生的,在多个事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。

假设有一个记录索引包含键值 4 和 7,不同的事务分别插入 5 和 6,每个事务都会产生一个加在 4-7 之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。如果有间隙锁了,插入意向锁会被阻塞。

索引

在问完锁之后,面试官问我对索引有多大的认识。我说索引的目的是为了提高查询效率,是用空间换时间的一种方式,索引会增加数据库写数据的时间,但是能大大提高查数据的时间。我本来以为还会问关于索引的相关内容,但似乎并没有,可能面试官听我说出B树和B+树的时候以为我对这块没啥问题吧。

有状态&无状态

在面试的过程中,我一直在有意无意地提及“无状态”这个名词。面试官有让我详细说说我对无状态的理解。我回答说:

不耦合其他系统,单纯地作为一个工具,根据输入提供输出,对于不同来源的同一输入,总是给出相同的输出值。

不过看起来面试官并不满意我的回答,他问我是从哪里学习到这个名词的。其实我也是在开发的过程中,从其他同事那听到的,耳濡目染就随口而出了,也没有真正地去了解背后的含义。不过他后面又问我在数据库上的有状态/无状态有没有了解过,只能说对这一块并没有认识。

后面去专门查询了相关资料,大体上,有状态的服务指的是服务器端需要保存请求的相关信息,每个请求可以默认地使用以前的请求信息。而无状态服务指的是服务器端所能够处理的过程必须全部来自于请求所携带的信息,以及其他服务器端自身所保存的、并且可以被所有请求所使用的公共信息

关于这点网上的资料还是比较齐全的,只是作为一个专业名词,自己并没有好好认真地去了解过。

最后:数据库单表合适的记录数

这篇文章快写完的时候,我突然想起了一个面试官问的比较有意思的问题,"MySQL单表的数据量维持到多少是一个比较好的情况?"

对于这个问题我现在还是比较懵,当时慌乱之中回答说大概在千万级吧。在面试官追问“为什么”下,我也只能说实在实践下找到的一个比较合适的值,不知道各位有没有更好的答案呢?