数据库无处不在,而且它们会一直存在。如果您对关系数据库比较熟悉,那么您可能对事务比较熟悉。在使用数据库时,事务是非常强大的工具。它们允许多个用户在处理同一数据库时很好地相互配合。然而,随着越来越多的用户连接到同一个数据库,您迟早会遇到性能问题。
尽管交易很强大,但它是一把双刃剑。如果使用不当,它们可能会耗尽您的数据库资源。事务隔离级别是微调I(隔离)位ACID(原子性、一致性、隔离性、持久性)的一种方式。如果您愿意在隔离的实际含义上做出妥协,则可以增加并发性。
首先,会出什么问题呢?假设每个语句都遵守ACID原则(确实如此),当不同用户同时运行多个语句时,您可能会遇到如下问题:
脏读取-数据在更新过程中被读取。例如:Bob更改了一行,这是事务的一部分。Alice读取该行并获取保存在数据库中的数据。然后,Bob回滚事务。在这种情况下,Alice拥有技术上不存在的数据。
不可重复读取-后续读取不会返回一致的结果。例如:Alice启动一个读取一行数据的事务。Bob更改该行并提交更改。当在同一事务中时,Alice再次读取同一行,并获得数据的新值。
幻影读取-后续SELECT语句可能返回不同的结果。例如:Alice使用返回多行的WHERE子句运行SELECT语句。Bob插入一个与Alice之前运行的语句的WHERE子句相匹配的行,并提交结果。Alice运行与她之前运行的相同的SELECT语句,现在她得到了新的行。
我们看到了可能会出错的地方,现在让我们看看我们如何才能防御它。首先是工具:数据库锁。一旦事务获取了对资源的锁定,它们就可以通过阻止其他事务获取其锁定来限制其他事务对该相同资源的访问。这一点,再加上每个事务在进行操作之前都需要获取适当的锁的事实,为我们提供了一个相当全面的可以操作的机制。
页级:这是一个中间锁(在行和表之间),适用于页-表行的子集,通常是在单个磁盘操作中可以从磁盘读取的数据量。
共享锁定,也称为读锁定。当事务想要读取数据(即SELECT操作)时,通常会请求资源上的共享锁。多个事务可以获取对资源的共享锁定,这意味着多个事务可以同时读取数据。
排他锁,也称为写锁。排他锁一次只能由单个事务获取。如果存在另一个拥有资源锁(共享或独占)的事务,则该事务将等待另一个事务释放锁。当一个事务持有排他锁时,任何其他事务都不能获取对该资源的任何锁(共享的或独占的)。
更新锁是排他锁更宽松的版本。可以在具有共享锁的对象上获取更新锁。当事务准备好更新对象时,它会将更新锁转换为排他锁。请注意,可以在具有共享锁的对象上获取更新锁,但不能在具有更新锁的对象上获取共享锁。
事务利用这些锁来实现不同的隔离级别。在非常高的级别上,隔离级别是平衡并发性和隔离的一种方式。隔离级别越高,锁的限制性就越强,因此获得所述锁需要等待的事务就越多,这将导致事务总数减少。正如他们所说,天下没有免费的午餐。以下是事务隔离级别:
这是最宽松的隔离级别。它允许当前事务读取其他事务尚未提交的数据。事务不会获取任何资源锁。这可能会导致读取正在被其他事务修改的内容。
事务只能读取已提交的数据。该事务在读取资源时获取共享锁,并在读取语句完成后立即释放它们。这意味着我们只读取已完成更新的数据,但是因为我们在每条指令之后释放锁,所以在后续读取时可能会得到不同的结果。
当事务想要读取资源时,它会获取共享锁,并将锁保持到事务结束。因为我们在整个事务中保持共享锁,所以我们确保所有读取都返回相同的数据,因为我们要防止其他事务在事务结束之前修改数据。
事务内的所有读取都返回事务开始时可用的数据。这就好像事务在开始时拍摄数据的快照,并在整个事务中使用该快照。它在功能上类似于下面描述的可序列化级别,但它使用了一种略有不同的机制来实现这一点。
这是可用的最高隔离级别。事务获取需要读取的资源的读锁定,并在事务持续时间内保留它们,从而确保数据在读取后在事务持续时间内保持不变。它还在更新数据时获取排他锁,并在事务结束时释放它们。此外,事务会对当前事务中匹配条件的任何行设置行范围锁,这样我们就可以避免幻影读取。
总而言之,每次在代码中使用事务时,都应该花点时间考虑所需的适当隔离级别。您可以将所有内容都设置为可序列化,然后就到此为止,但在大多数情况下,不需要这么强的限制,这样只会给数据库带来不必要的延迟。对于大多数情况,在提交读取和可重复读取之间进行选择就足够了。把这张桌子牢记在心,因为它会帮助你做出决定。始终使用您的应用程序可以承受的最宽松的隔离级别。