百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Lua脚本和Redis事务哪种方式在保证原子性方面性能更高?

wptr33 2025-01-17 13:13 24 浏览

通过之前的介绍我们知道了在Redis中可以通过Lua脚本和Redis的事务操作都能够保证命令执行的原子性操作,但是二者在实现机制以及使用性能上却是有着明显的不同,下面我们就来详细介绍一下两种方式的区别与联系,方便开发者能够更好的选择合适的方式来实现涉及到Redis的原子性操作场景。

Lua脚本

??Lua脚本是Redis中通过EVAL或者是通过EVALSHA命令执行的一种操作,这种操作允许客户端在Redis服务端直接执行自定义的Lua脚本,通过这种方式可以提高操作的的灵活性,保证操作的原子性,减少了客户端与服务端之间的通信连接次数,提高了命令执行效率。在之前的介绍中,我们也分析了Lua脚本在Redis客户端中执行的事原子性的操作,也就是说整个脚本的会被当做一条命令来执行,要么执行全部成功,要么全部失败,不会出现部分成功部分失败的结果,这样开发者就不会担心因为并发问题而导致数据不一致的情况出现。

Lua脚本的基本概念

??根据之前的介绍,我们知道Lua脚本其实是在Redis服务端中执行的,并且在执行Lua脚本的过程中,不会被其他客户端的操作命令所打断,也就是说,一旦脚本命令开始执行,那么Redis会保证不会有其他的客户端命令在其执行过程中对脚本进行干扰,这样就可以有效的保证原子性操作,即使在脚本执行的过程中,有其他的客户端发送了执行命令的请求,那么Redis也会等待脚本执行完成之后才会调用其他请求执行命令。

??从上面的分析中我们也可以看到,既然是个脚本,那么必然就会包含很多的执行步骤。通过脚本执行Redis命令可以有效的减少Redis的网络访问次数,例如原本执行三次网络访问命令的的过程,现在我们就可以通过一个脚本就可以搞定,如下所示。

if redis.call('exists', KEYS[1]) == 1 then
    return redis.call('set', KEYS[1], ARGV[1])
else
    return redis.error_reply('Key does not exist')
end

??因此,对于需要同时执行多个操作命令的过程,我们可以通过编写Lua脚本的方式来实现,这种方式通常比依赖Redis事务更加高效,因为在Redis事务中是不支持这种的复杂判断逻辑的。

??这里或许会有人问,既然是分开的几步操作,那么会不会因为并发问题导致数据不一致,例如在操作到第三步操作的时候突然断开操作了,第三步操作还没来得及操作,这就会导致数据不一致性的情况发生,这里在之前的分享中,我们提到过Lua脚本在Redis的操作中是一个原子性的,也就是说这些所有的命令只会有两个结果,成功和失败,不会出现中间状态。

EVAL命令和EVALSHA命令介绍

??EVAL命令是执行Lua脚本的操作命令,其语法如下所示。

EVAL script numkeys key1 key2 ... keyN arg1 arg2 ... argM

??其中,各个参数含义如下所示。

  • script:Lua 脚本的代码内容。
  • numkeys:要传递给脚本的键的数量。
  • key1 key2 ... keyN:在脚本中使用的Redis中的键。
  • arg1 arg2 ... argM:传递给脚本的参数,这里需要注意,这个参数是不包含键的。

??如下所示,给出一个简单的示例代码

EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 mykey "hello"

??这个脚本执行完成之后,会将mykey的值设置为hello

??EVALSHA这个命令是用来执行已经预先加载到Redis中的Lua脚本的命令,其基本语法如下所示。

EVALSHA sha1 numkeys key1 key2 ... keyN arg1 arg2 ... argM

??其中各个参数含义如下所示。

  • sha1:是脚本的SHA1校验和,这个值可以通过 SCRIPT LOAD 命令计算得到。
  • numkeyskey1key2 等与 EVAL 命令相同。

??示例代码如下所示

EVALSHA "d2d2bcd5b8a5ad12a59d87a679e6f5e5758b79e8" 1 mykey "world"

??这段命令就可以通过执行之前执行过的SCRIPT LOAD命令加载到Redis中的Lua脚本其中d2d2bcd5b8a5ad12a59d87a679e6f5e5758b79e8其实就是脚本的SHA1校验和。

??SCRIPT LOAD命令其实就是用来将Lua脚本加载到Redis中的操作,然后执行成功之后会返回SHA1的校验和,这样我们就可以通过这个校验和加上EVALSHA命令来调用该脚本了,就可以避免每次都发送大段的脚本代码,如下所示。

SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"

??当然除了这些之外,Redis内部还支持了对Lua脚本的高级支持,有兴趣的读者可以深入了解。

性能问题

??通过对Lua执行原理的介绍,我们也可以知道,虽然Lua脚本的执行是原子性的,但是但是如果在脚本执行的时候包含了特别耗时的操作,这个时候,可能会对Redis服务性能有所影响,Redis也是考虑到了此方面的弱点,所以提供了一个配置来设置Lua脚本执行的时间,如果超过了这个设置的脚本执行时间,那么Lua脚本就不会管脚本是否正常执行,直接返回执行失败,这里需要注意,这个执行失败所代表的含义就是所有的操作都是执行失败的,不会说出现部分结果成功,部分结果失败。

??同样,由于是脚本执行,所以对于执行操作中出现的一些不可预见的错误也就没有办法及时的进行处理。

Redis事务

??所谓的事务就是将一组命令放到一起通过原子性的方式进行执行,确保这些命令要么全部执行成功,要么就是全部失败。但是对于Redis中的事务来讲,与传统的关系型数据库不同的是对于事务隔离性、事务执行方式、事务错误处理等方便的支持。Redis的事务操作主要是基于MULTIEXECWATCHDISCARD 等命令实现,也没有提供关系型数据库类似的回滚机制,在执行操作的过程中事务命令会依次被存入到一个命令执行队列中按照顺序进行执行,如果某些命令执行失败了,那么其他的命令依然会执行,这样Redis的事务提供了对于原子性的保证,但是没有隔离性和持久性。

Redis事务命令

??在Redis中,涉及到事务操作的命令主要有如下一些。

  • MULTI 命令主要用于启动事务,从这个命令开始之后的所有命令都会被标记为事务操作的部分,一直到 EXEC 命令执行之前。
  • EXEC 命令用于执行事务队列中的所有命令。这些命令都是原子执行的。执行成功之后,EXEC 命令返回执行结果的列表。
  • DISCARD 命令用于放弃当前事务,清除事务队列。调用 DISCARD 后,事务中的所有命令将不再执行,Redis 会返回 "OK"。
  • WATCH 命令用于监视一个或多个键,一旦事务开始之前,某个被监视的键发生变化,那么 EXEC就会执行失败,事务也就不会被执行。
  • UNWATCH 命令用于取消对一个或多个键的监视。

Redis事务执行流程

??Redis 事务的执行流程如下所示

  • 开始事务:通过 MULTI 命令来开启一个事务的执行操作,Redis会将其后的所有命令都排列到一个事务命令执行队列中。
  • 执行事务命令:在 MULTIEXEC 之间所有的命令都会被客户端发送到事务队列中,但是这些命令并不会立即被执行。
  • 提交事务:通过 EXEC 命令提交事务,Redis会按照序依次执行事务队列中的所有的命令。然后EXEC 会返回一个列表,包含每个命令的执行结果。
  • 取消事务:如果在事务开始后决定不执行事务中的命令,我们就可以通过调用 DISCARD 命令来取消事务执行操作,然后清除所有队列中的命令。

??从上面的分析中我们可以看出,Redis的事务操作也可以有效的保证事务执行的原子性,也就是说事务中的命令要么完全执行成功,要么完全失败,无论事务中包含了多少的命令,都会被Redis逐一的执行,这样就保证了事务操作的原子性,这里所谓的原子性是指,将这些命令合并到一起执行,这个执行的过程是不会被其他客户端的命令所打断的,这个与Lua脚本执行的原子性是一样的。

??但是需要注意,这里的原子性只保证了这一组命令的执行过程的原子性,并不会像是关系型数据库那样,提供了回滚机制。所以也不会保证严格的持久性以及像是关系型数据库那样的ACID的保障。

??在支持复杂逻辑处理方面,由于Redis脚本只是简单的命令组合并不支持太多复杂的逻辑处理,所以使用起来不是太方便,虽然从Redis5之后引入了事务的隔离性但是比起Lua脚本的精确控制还是稍逊一些。

总结

??通过上面的分析,我们也知道了在Redis中无论是Lua脚本还是通过事务操作都是可以保证原子性的,但是这里所提到的原子性只是保证了一组操作过程在Redis中执行的时候不会被其他的客户端的命令操作所打断。但是有一点Lua脚本要比Redis事务机制要好,就是在执行操作的性能上,Lua脚本的性能要比事务操作更好,另外就是对于业务操作的灵活性上,Redis事务只是简单的命令组合,而Lua脚本则是提供了更加复杂的逻辑处理。因此Lua脚本在保证原子性方便和复杂逻辑处理方面表现会更优。

相关推荐

MySQL进阶五之自动读写分离mysql-proxy

自动读写分离目前,大量现网用户的业务场景中存在读多写少、业务负载无法预测等情况,在有大量读请求的应用场景下,单个实例可能无法承受读取压力,甚至会对业务产生影响。为了实现读取能力的弹性扩展,分担数据库压...

Postgres vs MySQL_vs2022连接mysql数据库

...

3分钟短文 | Laravel SQL筛选两个日期之间的记录,怎么写?

引言今天说一个细分的需求,在模型中,或者使用laravel提供的EloquentORM功能,构造查询语句时,返回位于两个指定的日期之间的条目。应该怎么写?本文通过几个例子,为大家梳理一下。学习时...

一文由浅入深带你完全掌握MySQL的锁机制原理与应用

本文将跟大家聊聊InnoDB的锁。本文比较长,包括一条SQL是如何加锁的,一些加锁规则、如何分析和解决死锁问题等内容,建议耐心读完,肯定对大家有帮助的。为什么需要加锁呢?...

验证Mysql中联合索引的最左匹配原则

后端面试中一定是必问mysql的,在以往的面试中好几个面试官都反馈我Mysql基础不行,今天来着重复习一下自己的弱点知识。在Mysql调优中索引优化又是非常重要的方法,不管公司的大小只要后端项目中用到...

MySQL索引解析(联合索引/最左前缀/覆盖索引/索引下推)

目录1.索引基础...

你会看 MySQL 的执行计划(EXPLAIN)吗?

SQL执行太慢怎么办?我们通常会使用EXPLAIN命令来查看SQL的执行计划,然后根据执行计划找出问题所在并进行优化。用法简介...

MySQL 从入门到精通(四)之索引结构

索引概述索引(index),是帮助MySQL高效获取数据的数据结构(有序),在数据之外,数据库系统还维护者满足特定查询算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构...

mysql总结——面试中最常问到的知识点

mysql作为开源数据库中的榜一大哥,一直是面试官们考察的重中之重。今天,我们来总结一下mysql的知识点,供大家复习参照,看完这些知识点,再加上一些边角细节,基本上能够应付大多mysql相关面试了(...

mysql总结——面试中最常问到的知识点(2)

首先我们回顾一下上篇内容,主要复习了索引,事务,锁,以及SQL优化的工具。本篇文章接着写后面的内容。性能优化索引优化,SQL中索引的相关优化主要有以下几个方面:最好是全匹配。如果是联合索引的话,遵循最...

MySQL基础全知全解!超详细无废话!轻松上手~

本期内容提醒:全篇2300+字,篇幅较长,可搭配饭菜一同“食”用,全篇无废话(除了这句),干货满满,可收藏供后期反复观看。注:MySQL中语法不区分大小写,本篇中...

深入剖析 MySQL 中的锁机制原理_mysql 锁详解

在互联网软件开发领域,MySQL作为一款广泛应用的关系型数据库管理系统,其锁机制在保障数据一致性和实现并发控制方面扮演着举足轻重的角色。对于互联网软件开发人员而言,深入理解MySQL的锁机制原理...

Java 与 MySQL 性能优化:MySQL分区表设计与性能优化全解析

引言在数据库管理领域,随着数据量的不断增长,如何高效地管理和操作数据成为了一个关键问题。MySQL分区表作为一种有效的数据管理技术,能够将大型表划分为多个更小、更易管理的分区,从而提升数据库的性能和可...

MySQL基础篇:DQL数据查询操作_mysql 查

一、基础查询DQL基础查询语法SELECT字段列表FROM表名列表WHERE条件列表GROUPBY分组字段列表HAVING分组后条件列表ORDERBY排序字段列表LIMIT...

MySql:索引的基本使用_mysql索引的使用和原理

一、索引基础概念1.什么是索引?索引是数据库表的特殊数据结构(通常是B+树),用于...