图片来源:storyset
当大多数人想到关系型数据库时,通常首先想到的是MySQL。MySQL使用InnoDB作为其存储引擎,可重复读取隔离级别是最常见的,它可以在事务开始前看到数据。
但与PostgreSQL不同的是,InnoDB中的可重复读取隔离级别无法顺利地处理某些问题。也就是说,在InnoDB的可重复读取隔离级别无法处理诸如丢失更新和幻象读取等问题,而在PostgreSQL中,你不需要额外的黑客来解决丢失更新问题。然而,有一些技巧你可以用来执行幻象读取,如范围类型和其他机制。
MySQL开发人员应该了解可能的陷阱,并能够适当地解决它们,以避免丢失更新和幻象读取等问题。本文将介绍MySQL开发人员如何解决幻象读取可能导致的 "写入 "偏移问题。
导致幻象读取的场景
有不同的场景可以导致幻象读取。然而,一般来说,这些情况都遵循一个类似的模式。它们最初会在MySQL数据库中搜索一个特定的范围,然后根据搜索到的范围的结果,进行CREATE、UPDATE或DELETE。之后,所执行的操作直接影响到从被搜索的范围中获取的结果。
例如,让我们假设在一个特定的范围被搜索后采取的行动是UPDATE或DELETE。在这种情况下,MySQL开发人员可以使用一个独占锁来避免 "写 "的偏差。然后,开发人员可以在SELECT的开始处使用FOR UPDATE,之后他们可以强制两个同时进行的事务一前一后。因此,这两个同时进行的事务,在竞赛条件下,规避了 "写 "的偏斜。
不过,如果我们假设根据特定范围的搜索结果采取的行动是CREATE,那么上面的解决方案是不完整的:没有相应的行存在,允许开发人员在SELECT中锁定,这意味着以后会形成一个行。
解决使用CREATE时的幻象读取问题
我们将介绍一个实际的场景,以更好地理解使用CREATE时,幻象读取所导致的问题。一旦我们描述了这个例子,我们就会介绍相应的解决方案。
设想一个允许人们预订会议室的系统;在有人使用这个系统预订了一个房间后,新的数据被添加到一个表中。这个系统让用户知道,根据他们想要预订的时间段,一个房间是可以使用的。一旦有人创建了一个新的预订条目,所有其他用户就可以避免时间冲突。
然而,当两个人需要在同一时间同时预订一个房间时,问题就出现了。两个用户都能通过最初的SELECT验证,这意味着在纸面上,他们都有同一时间的预订,导致时间冲突。例如,如果有多个用户通过VPN连接到远程SQL服务器,需要使用这个预订系统,这个问题就会更加复杂。即使MySQL开发人员添加了一个独占锁,这个问题也是无法避免的,因为他们无法在开始的SELECT验证时锁定一条记录。
用唯一约束索引解决
MySQL开发人员不能通过使用独占锁将一个并发操作变成一个顺序的操作。因此,他们需要通过向表添加唯一约束来让一个操作失败。
开发人员可以用唯一约束索引针对房间预订表中对应房间号和会议开始时间的列。这个解决方案可以防止有人预订别人已经预订过的时间段,而且开发者可以让任何人都不能预订一个超过一小时的房间。
然而,如果两个用户的会议时间重叠,这种解决方案也会使唯一约束无法生效。为了正确解决这个问题,开发人员必须将冲突具体化。
通过具体化冲突来解决
解决我们所讨论的幻读的正确方法是通过发现表所隐藏的冲突。开发人员可以用协调并发操作的数据集预先填充一个全新的表。如果我们以我们的会议室系统为例,我们可以想象创建一个新的表,支配时间段并提前显示所有可用的时间段。
有了这个新的表,开发人员现在要在决定可用时间段的列上执行一个SELECT,并包括一个FOR UPDATE,因为数据已经存在。开发人员需要在最初的SELECT之前运行这个FOR UPDATE。
通过实现上述例子中的冲突,开发者可以用一个排他性的锁阻止任何两个相互重叠的保留时间段,因此迫使一个时间段出现在另一个时间段之前或之后。由于第一个时间段的完成,无论哪一个时间段来得晚,都会立即失败。
结论
尽管将冲突物化是一个艰难且不直观的解决方案,但在使用MySQL数据库时,为了避免牺牲任何显著的性能水平,这是必要的。不幸的是,MySQL的InnoDB隔离级别不是可序列化的,所以开发者需要牺牲一定程度的复杂性来获得可接受的性能水平。
任何使用数据库的人都必须了解该数据库的能力以及其难以解决的问题。否则,就不可能预见到该数据库的哪些行为可能会潜在地损害数据库的设计和开发工作。
此外,了解如何适当地处理潜在的风险也同样重要。虽然我们在本文中通过我们的时间预订系统所描述的用例与其他用例并不完全相同,但它所表现出来的模式是足够相似的,了解如何解决这些问题可以使处理其他情况变得更加容易。