当前位置:和仁网 >聚合 > 正文

为何Redis用乐观锁,而MySQL数据库却没有

2021-08-06 0

简单来说,Redis使用乐观锁,相对于悲观锁,在实现中更加简单,在某些场景中的性能也更好。Redis作为一个轻量级的、快速的缓存引擎,而不是一个全功能的关系型数据库,既没有使用悲观锁的必要,也难以承受使用悲观锁的成本。
详细来说,要深入到Redis和MySQL的事务处理机制。Redis关于事务的文档见此:
Transactions(事务)
Redis对于事务只提供了非常有限的支持,其实更多地是试图绕过问题。
首先,Redis对于同一事务中的一组操作,而不是立即执行,而是放入一个queue中,当执行到EXEC时,再一起执行。事务执行是全局独占的,也就是同一时间只有一个事务被执行,中途不能被其它事务打断。Redis用这种最简单的、也是性能最差的方式避免了race
condition。
其次,在Redis的事务中,如果有一个或多个操作失败,其它操作仍然会成功,也就是说它根本没有回滚机制。
这种方式会带来很多严重的问题,其中之一是,无法先读取某个数值后再进行依赖这个值的操作,因为放在一个事务里会被在同一个瞬间执行,不放在同一个事务里又会导致race
condition。解决方法是使用WATCH,它会监视一个或多个变量,如果变量的值在调用WATCH以后和事务提交之前被别的事务修改过了,整个事务都会失败。这类似于操作系统中的CAS(Compare
and
Set)。我不知道WATCH具体是怎么实现的,但是我推测它监控了指定变量的版本号。
即使有了WATCH,Redis的事务也是受到严重限制的。第一,它没有实现读数据时的一致性,因为WATCH对于读操作不起作用。第二,它不支持回滚。第三,在对同一变量存在大量并发写操作时,性能会非常差,因为每次提交事务时,WATCH监控的变量都已经被修改了,导致事务将多次提交失败。但是,Redis本来就是一个KV类型的缓存引擎,要处理的是大量读少量写的场景,对一致性也没有要求。
MySQL就完全不一样了,作为一个典型的关系型数据库,它需要完整地实现ACID,所以Redis的方式是解决不了它的问题的。
MySQL中的MVCC机制(Oracle的也是),通过undo
日志来获取某个行记录的历史快照,从而实现了所谓的读一致性。它的目的是读取某个时间点上的历史数据(而不是可能已经被修改了的数据),而不是避免悲观锁的使用。严格地说这不能称之为乐观锁,因为它既不Compare当前版本和历史版本,也不进行Set。事实上,在读取记录的历史快照时,当前记录有可能(由于并发的写操作)已经被加上独占锁。
进一步的问题是:有没有可能使用乐观锁来实现RDBMS中的写一致性?有没有可能使用乐观锁实现完整的ACID特性?
回答是可以。例如,MS
SQL
SERVER的Hekaton引擎通过一套基于时间戳的多版本管理系统,实现了不使用了悲观锁的ACID。
但是,这并不意味着乐观锁必然优于悲观锁。除了维护多版本的开销以外,乐观锁无法避免的一个问题是,当多个写操作试图更新同一个对象时,只有第一个操作可以成功,其它的操作都会在Compare时失败然后回滚,从而造成极大的性能问题。在这种情况下,乐观锁的性能会低于悲观锁。
目前的趋势似乎是,大规模的分布式数据库更倾向于使用乐观锁来达到所谓的external
consistency,因为基于传统悲观锁的分布式锁在集群大到一定程度以后(从几百台扩展到成千上万台时),性能开销就大得无法接受了。Google的Spanner就是基于乐观锁。当然这完全是另外一个问题了。
本周热门
本月热门