使用redis+lua实现SQL中的select intersect的效果
1.需求
业务中需要实现在两个集合中搜索数据,并返回交集。
用SQL的伪代码可以描述如下:
select key from set1 where sorted_key between min and max
INTERSECT
select key from set2 where sorted_key between min and max
2.现有存储格式
业务使用了redis的有序集合(sorted set)来存储数据:
set1 ->
field1 -> value1
field2 -> value2
set2 ->
field1 -> value1
field2 -> value2
常规的思路是:
# 分页拉取KEY1,然后分页拉取KEY2,然后计算交集
ZRANGEBYSCORE set1 ${min} ${max} WITHSCORES LIMIT 0 1000
ZRANGEBYSCORE set1 ${min} ${max} WITHSCORES LIMIT 1000 1000
3.lua实现
看了一下redis的lua脚本功能,可以完全在redis服务器端完成:
--like sql: insert ...select, 实现类似SQL中的insert select语句
local function select_and_insert(from_key, min_value, max_value, to_key)
local cnt = redis.call("ZCOUNT", from_key, min_value, max_value)
local start = 0
local limit = 1000
while(start<cnt)
do
local value = redis.call("ZRANGEBYSCORE", from_key, min_value, max_value, "WITHSCORES", "LIMIT", start, limit)
start = start + (#value)/2
local idx = 1
while( idx<=#value )
do
value[idx], value[idx+1] = value[idx+1], value[idx]
idx = idx + 2
end
local ret = redis.call("ZADD", to_key, unpack(value))
if(ret==false)
then
return false, "zadd fail"
end
end
return true,"success"
end
--like select intersect,实现类似SQL中的select intersect
local function intersect(key1, min1, max1, key2, min2, max2, to_key)
local temp_key_1 = "___temp_1"
redis.call("DEL", temp_key_1)
local ret,msg = select_and_insert(key1, min1, max1, temp_key_1)
if(ret==false)
then
return false, key1.." fail:"..msg
end
--
local temp_key_2 = "___temp_2"
redis.call("DEL", temp_key_2)
ret,msg = select_and_insert(key2, min2, max2, temp_key_2)
if(ret==false)
then
redis.call("DEL", temp_key_1)
return false, key2.." fail:"..msg
end
--
ret = redis.call("ZINTERSTORE", to_key, 2, temp_key_1, temp_key_2)
redis.call("DEL", temp_key_1)
redis.call("DEL", temp_key_2)
if(ret==false)
then
return false, "ZINTERSTORE fail"
end
return true, string.format("intersect count is %d", ret)
end
local function main()
return intersect("set1", 10, 100, "set2", 200, 400, "my_result")
end
return main()
在命令行测试一下:
redis-cli -h 192.168.0.2 -p 6379 -a "my_password" --eval test_intersect.lua
达到了预期的效果。
4.做一个script load工具
按照网上的方法这样导入脚本,始终不成功:
redis-cli -h 192.168.0.2 -p 6379 -a "my_password" SCRIPT LOAD "`cat test_intersect.lua`"
于是用golang基于gin框架来做一个脚本保存功能:
上传表单:
//注册
my_gin.GET("/redis_script_form", redisScriptForm)
func redisScriptForm(c *gin.Context){
c.Data(200, "text/html; charset=utf-8", []byte(`
<html>
<body>
<form method="POST" enctype="application/x-www-form-urlencoded" action="/redis_script_load">
<textarea name="lua" style="width:100%; height:300px"></textarea><br/>
<input type="submit" value="upload script"/>
</form>
</body>
</html>
`))
}
保存接口
//
import "github.com/go-redis/redis/v7"
//
my_gin.POST("/redis_script_load", redisScriptLoad)
func redisScriptLoad(c *gin.Context){
code := c.PostForm("lua")
redis := utils.GetRedisClient()
val, err := redis.ScriptLoad(code).Result()
if err!=nil{
c.Data(200, "text/plain", []byte(fmt.Sprintf("redis.ScriptLoad fail:%s", err.Error() )))
return
}
c.Data(200, "text/plain", []byte(fmt.Sprintf("redis.ScriptLoad success:sha=%s", val )))
}
保存成功会返回代码的SHA hash值。
5.用golang调用redis中的lua脚本
lua的main()改一改
首先要再修改之前lua代码中的main(),不要写死参数:
local function main()
local set1_param={KEYS[1], ARGV[1], ARGV[2]}
local set2_param={KEYS[2], ARGV[3], ARGV[4]}
local temp_key = "__temp_3"
--
redis.call("DEL", temp_key)
local ret,msg = intersect(set1_param[1], set1_param[2], set1_param[3], set2_param[1], cpu_param[2], set2_param[3], temp_key)
if(ret==false)
then
return msg
end
local values = redis.call("ZRANGE", temp_key, "0", "100000000000")
redis.call("DEL", temp_key)
return values
end
命令行测试
再次保存代码,得到SHA值。
用命令行进行测试:
EVALSHA f2f7d1b5439b8bb4c8320a7dce4b54c133a3d47d 2 "set1" "set2" "10" "1000" "100" "3000"
得到了预想的结果。
golang代码测试:
//注册
my_gin.GET("/redis_eval_script", redisEvalScript)
func redisEvalScript(c *gin.Context){
redis := utils.GetRedisClient()
val, err := redis.Do("EVALSHA",
"f2f7d1b5439b8bb4c8320a7dce4b54c133a3d47d",
2, "set1", "set2", 10, 1000,
100, 3000).Result()
if err!=nil{
ResponseError(c, err.Error())
return
}
j,err := json.Marshal(val)
if err!=nil{
c.Data(200, "text/plain", []byte(fmt.Sprintf("EVALSHA:%s", err.Error() )))
return
}
c.Data(200, "text/plain; charset=utf-8",
[]byte(fmt.Sprintf("json:\n%s", string(j))))
}
have fun!
======
2020-05-11补充:
- 这个东东可以玩玩,使用前需要认真测试
- 建议运行在slave上,不要在master上
- 创造临时KEY的时候可能会产生大量的IO,而目前我还没找到一种临时KEY只存在于内存的方法
使用redis+lua实现SQL中的select intersect的效果的更多相关文章
- 教您如何使用SQL中的SELECT LIKE like语句
LIKE语句在SQL有着不可替代的重要作用,下文就将为您介绍SQL语句中SELECT LIKE like的详细用法,希望对您能有所帮助. LIKE语句的语法格式是:select * from 表名 w ...
- mysql中select into 和sql中的select into 对比
现在有张表为student,我想将这个表里面的数据复制到一个为dust的新表中去.answer 01: create table dust select * from student;//用于复制前未 ...
- 关于SQL中SELECT *(星号)的危害论
听闻有许多人是禁止开发人员在SQL中使用SELECT *的,这里翻译一下StackOverflow的一篇提问,个人认为相当客观 [SELECT *]危害主要有以下几点: 给数据消费者传数据的低效.当你 ...
- oracle PL/SQL(procedure language/SQL)程序设计(在PL/SQL中使用SQL)
在PL/SQL程序中,允许使用的SQL语句只有DML和事务控制语句,使用DDL语句是非法的.使用SELECT语句从数据库中选取数据时,只能返回一行数据.使用COMMIT, ROLLBACK, 和SA ...
- Mysql训练:两个表中使用 Select 语句会导致产生 笛卡尔乘积 ,两个表的前后顺序决定查询之后的表顺序
力扣:超过经理收入的员工 Employee 表包含所有员工,他们的经理也属于员工.每个员工都有一个 Id,此外还有一列对应员工的经理的 Id. +----+-------+--------+----- ...
- 正则表达式小技巧,sql中in的字符串处理
工作中我经常写sql,当写带in的语句时,需要敲好多单引号,逗号,敲写起来容易易出错.因此,我写了一个小工具,处理这种繁琐工作.原理简单,利用正则表达式匹配.替换. 先看界面,一个html页面,包含三 ...
- 【代码分享】用redis+lua实现多个集合取交集并过滤,类似于: select key from set2 where key in (select key from set1) and value>=xxx
redis中的zset结构可以看成一个个包含数值的集合,或者认为是一个关系数据库中用列存储方式存储的一列. 需求 假设我有这样一个数据筛选需求,用SQL表示为: select key from set ...
- SQL中SET和SELECT赋值的区别
最近的项目写的SQL比较多,经常会用到对变量赋值,而我使用SET和SELECT都会达到效果. 那就有些迷惑,这两者有什么区别呢?什么时候哪该哪个呢? 经过网上的查询,及个人练习,总结两者有以下几点主要 ...
- sql 中set和select区别
基于SQL中SET与SELECT赋值的区别详解 2012年09月06日 ⁄ 综合 ⁄ 共 912字 ⁄ 字号 小 中 大 ⁄ 评论关闭 最近的项目写的SQL比较多,经常会用到对变量赋值,而我使用SET ...
随机推荐
- SP8374 PARKET1 - PARKET 题解
Content 有一个 \(l\times w\) 大小的网格,其四周均被染成了红色,其余部分是棕色,已知红色网格与棕色网格的数量,求 \(l\) 与 \(w\) 的值. Solution 接下来给各 ...
- JAVA调用阿里云短信接口
官方文档:https://help.aliyun.com/document_detail/101414.html?spm=a2c4g.11186623.6.626.2cef6220yxh5l7 jar ...
- JAVAWEB项目报"xxx响应头缺失“漏洞处理方案
新增一个拦截器,在拦截器doFilter()方法增加以下代码 public void doFilter(ServletRequest request, ServletResponse response ...
- 【LeetCode】663. Equal Tree Partition 解题报告 (C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcode ...
- Python Revisited Day 09 (调试、测试与Profiling)
目录 9.1 调试 9.1.1 处理语法错误 9.1.2 处理运行时错误 9.1.3 科学的调试 9.2 单元测试 9.3 Profiling 9.1 调试 定期地进行备份是程序设计中地一个关键环节- ...
- Spring企业级程序设计 • 【第4章 Spring持久化层和事务管理】
全部章节 >>>> 本章目录 4.1 配置数据源资源 4.1.1 JdbcTemplate介绍 4.1.2通过ComboPooledDataSource创建数据源 4.1. ...
- 使用 JavaScript 的 HTML 页面混合、根据在下拉列表框中选择的内容,决定页面效果,用户在下拉列表框中选择页面将要使用的背景颜色
查看本章节 查看作业目录 需求说明: 根据在下拉列表框中选择的内容,决定页面效果 用户在下拉列表框中选择页面将 要使用的背景颜色 当用户选择橙色时,页面背景将显示为橙色 实现思路: 用表单 <s ...
- 日志分析系统 - k8s部署ElasticSearch集群
K8s部署ElasticSearch集群 1.前提准备工作 1.1 创建elastic的命名空间 namespace编排文件如下: elastic.namespace.yaml --- apiVers ...
- CGO快速入门
1. 通过`improt "C"`语句开启CGO特性2. `/**/`中间是C代码,之后接 import "C" 如果存在空行 就会报错.could not d ...
- Flutter 让你的Dialog脱胎换骨吧!(Attach,Dialog,Loading,Toast)
前言 Q:你一生中闻过最臭的东西,是什么? A:我那早已腐烂的梦. 兄弟萌!!!我又来了! 这次,我能自信的对大家说:我终于给大家带了一个,能真正帮助大家解决诸多坑比场景的pub包! 将之前的flut ...