关系型数据库(RMDBS)
数据库中表与表的数据之间存在某种关联的内在关系,因为这种关系,所以我们称这种数据库为关系型数据库。 典型:Mysql/MariaDB、postgreSQL、Oracle、SQLServer、DB2、Access、SQLlite3 特点: 全部使用SQL(结构化查询语言)进行数据库操作。 都存在主外键关系,表,等等关系特征。 大部分都支持各种关系型的数据库的特性:存储过程、触发器、视图、临时表、模式、函数
非关系型数据库(NoSQL)
NOSQL:Not Only SQL的缩写,泛指非关系型数据库。 泛指那些不使用SQL语句进行数据操作的数据库,所有数据库中只要不使用SQL语句的都是非关系型数据库。 典型:Redis、MongoDB、elasticsearch、hbase、 Hadoop、图数据库。。。。 特点: 1. 每一款都不一样。用途不一致,功能不一致,各有各的操作方式。 2. 基本不支持主外键关系,也没有事务的概念。(MongoDB号称最接近关系型数据库的,所以MongoDB有这些的。)
一 redis的五种数据类型
一 五种数据类型
类型 | string(字符串类型) | hash(哈希类型) | list(列表类型) | set(无序集合) | zset(有序集合) |
说明 |
是 Redis 中最为基础的数据存储类型,它在 Redis 中是二进制安全的,也就是byte类型。 key: 值 |
用于存储对象/字典,对象/字典的结构为键值对。key、域、值的类型都为string。域在同一个hash中是唯一的。 key:{ |
它的子成员类型为string。 key: [ 值1,值2, 值3.....] |
它的子成员类型为string类型,元素唯一不重复,没有修改操作,可以添加/删除成员。 key: {值1, 值4, 值3, ...., 值5}
|
它的子成员值的类型为string类型,元素唯一不重复,没有修改操作,可以添加/删除成员。权重值从小到大排列。 key: {
|
设置操作 |
"""设置操作""" # 设置单个字符串数据 set key value # 不会过期 # 当键不存在时才能设置成功,用于一个变量只能被设置一次的情况。 setnx key value # 一般用于给数据加分布式锁,防止并发出现的数据不一致的情况。 # 设置多个字符串类型的数据 mset key1 value1 key2 value2 ... # 字符串拼接 append key value # append的key如果不存在,则相当于set添加了一个字符串数据。 |
"""设置操作""" # 设置指定键的单个属性 hset key field value hset user:1 name xiaoming |
# 添加子成员 # 在左侧(上方,前面)添加一条或多条数据 lpush key value1 value2 ... # 在右侧(上方,后面)添加一条或多条数据 rpush key value1 value2 ... # 在指定元素的左边(前免,上方)/右边(后面,下方)插入一个或多个数据 linsert key before 指定元素 value1 value2 .... linsert key after 指定元素 value1 value2 .... # 设置指定索引位置成员的值 lset key index value # 删除指定成员 lrem key count value # 注意: # count表示删除的数量,value表示要删除的成员。该命令默认表示将列表从左侧前count个value的元素移除 # count==0,表示删除列表所有值为value的成员 # count >0,表示删除列表左侧开始的前count个value成员 # count <0,表示删除列表右侧开始的前count个value成员 |
# 添加元素 sadd key member1 member2 ...
|
# 添加成员 zadd key score1 member1 zadd key score1 member1 score2 member2 score3 member3 .... # 给指定成员增加权重值 zincrby key score member |
修改操作 |
# 根据键获取字符串的值 get key # 根据多个键获取多个值 mget key1 key2 ... # 自增自减 incr num # 相当于num+=1 decr num # 相当于num-=1 # 获取字符串长度 strlen name # 比特流操作(使用场景:签到记录、布隆过滤器(防止缓存击穿的一种防范手段)、打卡记录、心跳检测。) BITCOUNT # 统计字符串的值被设置为1的bit数. BITPOS # 返回字符串里面第一个被设置为1或者0的bit位。 SETBIT # 设置一个bit数据的值 GETBIT # 获取一个bit数据的值
# 签到,id为1的用户2022年第7天签到 setbit user:1:checkin:2022 7 1 # 00000001 # 查询,用户1在2022年第7天是否上班 getbit user:1:checkin:2022 7 # 1 # 查询,用户1在2022年第3天是否上班 getbit user:1:checkin:2022 3 # 0 # 补签,用户1在2022年第4天补签 setbit user:1:checkin:2022 4 1 # 00001001 # 签到,用户1在2022年第15天签到 setbit user:1:checkin:2022 15 1 # 0000100100000001 # 统计,用户1在2022年一共签到了多少天 bitcount user:1:checkin:2022 # 3 # 查询,首次签到是什么时候 bitpos user:1:checkin:2022 1 # 4
|
# 获取hash的所有成员 hgetall key # 获取指定键所有的域/属性 hkeys key # 获取指定键的单个域/属性的值 hget key field # 获取指定键的多个域/属性的值 hmget key field1 field2 ... # 获取指定键的所有值 hvals key # 随机抽取一个属性 hrandfield key # 删除指定键的域/属性 hdel key field1 field2 ... # 判断指定属性/域是否存在于当前hash中 hexists key field # 属性值自增自减 hincrby key field number # 获取hash的成员数量 hlen key
# 设置指定键的属性/域 127.0.0.1:6379> hset user:1 name xiaoming # user:1 没有的key会自动创建,并因为使用hset,所以是一个hash数据类型 (integer) 1 127.0.0.1:6379> hset user:1 name xiaohong # user:1中重复的属性name会被修改,返回值为0,是因为没有新增属性。 (integer) 0 127.0.0.1:6379> hset user:1 age 16 # user:1中没有的属性会被新增 (integer) 1 127.0.0.1:6379> hset user:1 sex 1 classmate 301 # hset可以一次性增加多个成员。 # 注意:在redis旧版本中,设置多成员可以使用hmset,新版本没有hmset (integer) 2 # 获取hash的所有成员 127.0.0.1:6379> hgetall user:3 1) "name" 2) "xiaohong" 3) "age" 4) "17" 5) "sex" 6) "1" # 获取键user的所有域/属性 127.0.0.1:6379> hkeys user:1 1) "name" 2) "age" 3) "sex" 4) "classmate" # 获取指定键的单个域/属性的值 127.0.0.1:6379> hget user:3 name "xiaohong" # 获取指定键的多个域/属性的值 127.0.0.1:6379> hmget user:3 name sex 1) "xiaohong" 2) "1" # 获取指定键的所有值 127.0.0.1:6379> hvals user:1 1) "xiaoming" 2) "16" 3) "1" 4) "301" # 随机抽取一个属性 hrandfield user:3 # 删除指定键的域/属性 127.0.0.1:6379> hdel user:1 classmate age (integer) 2 # 判断指定属性/域是否存在于当前hash中 127.0.0.1:6379> hexists user:3 age (integer) 1 127.0.0.1:6379> hexists user:3 classmate (integer) 0 # 属性值自增自减 # 按指定数值自增 127.0.0.1:6379> hincrby user:3 age 2 (integer) 19 127.0.0.1:6379> hincrby user:3 age 2 (integer) 21 # 按指定数值自减 127.0.0.1:6379> hincrby user:3 age -2 (integer) 19 127.0.0.1:6379> hincrby user:3 age -2 (integer) 17 # 获取hash的成员数量 127.0.0.1:6379> hlen user:3 (integer) 3 |
# 根据指定的索引获取成员的值 lindex key index # 移除并获取列表的第一个成员或最后一个成员 lpop key # 左侧(上方、前面)第一个成员出列 rpop key # 右侧(下方、后面)最后一个成员出列 # lpush与lpop经常组合使用,可构建成一个栈列。rpush与lpop 或 lpush与rpop 也可以组成一个队列。 # 原子性的安全的队列 # 往列表team:4 右侧追加3个成员 rpush team:4 x y x # 把列表team:4的末尾最后一个成员,原子操作添加到team:5的左侧 rpoplpush team:4 team:5 # 获取列表的切片 闭区间 (start, stop) lrange key start stop # 获取列表的长度 llen key |
# 获取集合的所有成员 smembers key # 获取集合的长度 scard keys # 随机获取一个/多个成员 spop key [count=1] # 注意: # count为可选参数,不填则默认1个。被提取成员会从集合中被删除掉 # 删除指定元素 srem key value # 交集,差集和并集 sinter key1 key2 key3 .... # 交集,比较多个集合中共同存在的成员 sdiff key1 key2 key3 .... # 差集,比较多个集合中不同的成员,我有你(们)没有的 sunion key1 key2 key3 .... # 并集,合并所有集合的成员,并去重
# 基本数据 # user:1 = {1,2,3,4} sadd user:1 1 2 3 4 # user:2 = {1,3,4,5} sadd user:2 1 3 4 5 # user:3 = {1,3,5,6} sadd user:3 1 3 5 6 # user:4 = {2,3,4} sadd user:4 2 3 4 # 交集 127.0.0.1:6379> sinter user:1 user:2 1) "1" 2) "3" 3) "4" 127.0.0.1:6379> sinter user:1 user:3 1) "1" 2) "3" 127.0.0.1:6379> sinter user:1 user:4 1) "2" 2) "3" 3) "4" 127.0.0.1:6379> sinter user:2 user:4 1) "3" 2) "4" # 并集 127.0.0.1:6379> sunion user:1 user:2 user:4 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" # 差集 127.0.0.1:6379> sdiff user:2 user:3 1) "4" # 此时可以给user:3推荐4 127.0.0.1:6379> sdiff user:3 user:2 1) "6" # 此时可以给user:2推荐6 127.0.0.1:6379> sdiff user:1 user:3 1) "2" 2) "4" |
# 获取集合长度 zcard key # 获取指定成员的权重值 zscore key member # 获取指定成员在集合中的排名 srank key member # score从小到大的排名 zrevrank key member # score从大到小的排名 # 获取在指定score区间的所有成员数量【闭区间】 zcount key min max # 获取在指定score/index区间的所有成员 zrangebyscore key min max # 按score进行从低往高排序获取指定score区间 zrevrangebyscore key min max # 按score进行从高往低排序获取指定score区间 zrange key start stop # 按scoer进行从低往高排序获取指定索引区间 zrevrange key start stop # 按scoer进行从高往低排序获取指定索引区间 # 删除成员 zrem key member1 zrem key member1 member2 member3 .... # 删除指定数量的成员 # 删除指定数量的成员,从最低score开始删除 zpopmin key [count] # 删除指定数量的成员,从最高score开始删除 zpopmax key [count]
# 获取achievements中在(60,70)这个区间的数据 127.0.0.1:6379> zrangebyscore achievements 0 60 1) "xiaocao" 2) "xiaoqing" 3) "xiaolv" 127.0.0.1:6379> zrangebyscore achievements 90 100 1) "xiaobai" 2) "xiaolan" # 获取achievements中分数最低的3个数据 127.0.0.1:6379> zrange achievements 0 2 1) "xiaocao" 2) "xiaoqing" 3) "xiaolv" # 获取achievements中分数最高的3个数据 127.0.0.1:6379> zrevrange achievements 0 2 1) "xiaolan" 2) "xiaobai" 3) "xiaohui" |
常用的业务场景 | 字符串string: 用于保存一些项目中的普通数据,只要键值对的都可以保存,例如,保存 session/jwt,定时记录状态,倒计时、验证码、防灌水答案 | 哈希hash:用于保存项目中的一些对象结构/字典数据,但是不能保存多维的字典,例如,商城的购物车,文章信息,json结构数据 | 列表list:用于保存项目中的列表数据,但是也不能保存多维的列表,例如,消息队列,秒杀系统,排队,浏览历史 | 无序集合set: 用于保存项目中的一些不能重复的数据,可以用于过滤,例如,候选人名单, 作者名单 | 有序集合zset:用于保存项目中一些不能重复,但是需要进行排序的数据, 例如:分数排行榜, 海选人排行榜,热搜排行 |
开发中,redis常用的业务场景:
数据缓存、
分布式数据共享、
计数器、
限流、
位统计(用户打卡、签到)、
购物车、
消息队列、
抽奖奖品池、
排行榜单(搜索排名)、
用户关系记录[收藏、点赞、关注、好友、拉黑]、 ---> 无序集合
二 key操作
redis中所有的数据都是通过key(键)来进行操作,这里我们学习一下关于任何数据类型都通用的命令。
1 查找键
# 参数支持简单的正则表达式 keys pattern # 查看以n开头的键 keys n* # 查看以数字结尾的键 keys *[1-9]
2 设置键值的过期时间
setex key seconds value
3 查看键的有效期
ttl key # 结果结果是秒作为单位的整数 # -1 表示永不过期 # -2 表示当前数据已经过期,查看一个不存在的数据的有效期就是-2
4 设置key的有效期
# 给已有的数据重新设置有效期,redis中所有的数据都可以通过expire来设置它的有效期。有效期到了,数据就被redis服务端删除 expire key seconds
5 判断键是否存在(存在返回`1`,不存在返回`0`)
exists key1
6 查看键的数据类型
type key # none 空,当前数据不存在 # string 字符串 # hash 哈希类型 # list 列表类型 # set 无序集合 # zset 有序集合
7 key重命名
rename 旧的key 新的key
8 删除键以及键对应的值
# 删除一个key del name # 删除多个key del title num # 清空所有key(!慎用,一旦执行,则redis所有数据库0~15的全部key都会被清除) flushall
9 select切换数据库
redis的配置文件中,默认有0~15之间的16个数据库,默认操作的就是0号数据库
select <数据库ID>
10 auth认证
默认情况下我们刚安装的redis是无密码登陆的,同时也没有任何关于权限的概念。这种情况下则相当于使用同一个redis的所有开发者都共享了同一个账号信息,难免就会出现误操作把别人的key删掉或者数据泄露的风险。
10.1 开启密码认证(redis6.x以前的版本)
sudo vim /etc/redis/redis.conf # 打开行号 :set nu # 文件中1036行左右找到requirepass配置项 :1036 requirepass fuguang # 保存文件 :wq # 重启redis服务 sudo service redis-server restart
登录认证
AUTH <password>
在redis6.x以后新增了ACL的版本中依然可以使用这种只需要提供密码的认证模式,相当于使用了一个默认的超级用户 "default",通过这样的方式,实现了对低版本的兼容,所以上面的auth命令还存在2个参数的用法。
redis-cli -a passwd --raw
# 注意:使用了ACL认证机制,则原来的密码认证requirepass选项则有可能失效。 # 如果要关闭原来的密码认证。则重新注释掉之前requirepass选项即可。
常用命令
命令(大小写不区分) | 说明 | 用户管理 |
---|---|---|
|
查看命令帮助文档 | |
|
列出所有账户 |
acl users # 1) "default" |
|
列出所有账户的详情信息(用户名、用户状态、发布订阅管道权限、命令操作权限) |
acl list # 1) "user default on nopass ~* &* +@all" |
ACL WHOAMI |
我是谁,查看当前账户 |
acl whoami # "default" |
ACL SETUSER <用户名> |
设置账户 |
acl setuser fg # 列出账户详情,刚创建的账户是没有密码,没有激活off,没有任何权限的 acl list # 1) "user default on nopass ~* &* +@all" # 2) "user fg off resetchannels -@all" # 刚创建的用户是未激活的,所以要激活用户。on 表示激活,off表示未激活 # setuser 可用户创建账户,也可以用户给已有账户设置激活状态,权限等。 acl setuser fg on acl list # 序号) "user 用户名 激活状态 密码 权限" # 1) "user default on nopass ~* &* +@all" # 2) "user fg on resetchannels -@all" |
ACL DELUSER <用户名> |
删除账户 | |
ACL GENPASS |
创建密码 | |
ACL GETUSER <用户名> |
获得用户 |
acl getuser default # 1) "flags" # 特征:激活状态,无密码 # 2) 1) "on" # 2) "nopass" # 3) "passwords" # 密码:空,表示没有设置 # 4) (empty array) # 5) "commands" # (权限)可操作的命令类别范围:+@all表示可操作任意所有权限 # 6) "+@all" # 7) "keys" # (权限)可操作的具体key范围:~*表示任意key # 8) "~*" # 9) "channels" (权限)可使用的发布订阅管道: &*表示任意发布订阅管道都可以使用 #10) "&*" #11) "selectors" # (权限)上面的权限划分简写 #12) (empty array) |
ACL CAT |
查看权限类别 | |
ACL LOAD |
加载aclfile配置文件中的账户信息到redis中 | |
ACL SAVE/CONFIG REWRITE |
保存账户信息至aclfile配置文件中 | |
|
ACL List命令的列表选项参数:
参数 | 说明 |
---|---|
user <用户名> |
代表用户 |
|
代表用户是激活状态的,如果是off ,代表用户是禁用的,在off 状态下,用户会登录失败。 |
|
是sha256加密后的密码串,nopass 或没有任何密码,表示无密码。 |
|
~ 为添加指定模式的key(键)。
~* 代表任意键,是allkeys 的别名。
~<pattern> :将一个键模式添加到指定账户可以操作的键列表里,可以设定多个键模式:
%R~<pattern> :(Redis7.x开始支持)添加只读键模式;
%W~<pattern> :(Redis7.x开始支持)添加只写键模式;
%RW~<pattern> :(Redis7.x开始支持)~<pattern> 的别名;
allkeys :~* 的别名;
resetkeys :清空之前给当前账户分配的可操作的键模式列表。 |
|
&为redis的高级特性中的订阅发布通道权限 ,当然resetchannels 是无任何订阅发布通道权限的意思。
&* 表示可使用所有订阅发布通道权限。 |
|
设置用户可以或不可以使用某类别的命令,类别可以通过acl cat 获得。
+@ 代表用户可以使用指定类别命令,如果是 -@ ,代表用户无法使用该类别命令。
all 表示所有类别权限。 |
命令分配选项参数写法:
redis-cli # 查看权限类别 acl cat # 可以看到有21个类别。 # 查看权限子类别-字符串相关权限 acl cat string # 给fuguang用户增加权限,+@all表示全部权限。 acl setuser fuguang +@string # 分配用户可操作字符串类别的命令 # 切换账户 auth fuguang 123 # 操作字符串数据,会发生如下错误!主要原因是,我们虽然分配了操作的命令权限,没有没有分配key的操作权限。 set name xiaoming # (error) NOPERM this user has no permissions to access one of the keys used as arguments get name # (error) NOPERM this user has no permissions to access one of the keys used as arguments # 再次切换账户回到default auth default default # 分配key权限 acl setuser fuguang ~name # 只能操作name这个key,~name 等价于 %RW~name # 切换账户 auth fuguang 123 # 操作name set name xiaoming # ok get name # "xiaoming" # 操作其他key,因为没有操作权限,所以依然报错! set user xiaoming (error) NOPERM this user has no permissions to access one of the keys used as arguments # 再次切换账户 auth default default # 可以分配所有权限,所有key acl setuser fuguang ~* +@all # 切换账户 auth fuguang 123 # 执行权限范围的操作 set name xiaoming # 我是谁? acl whoami
参数 | 描述 |
---|---|
|
将命令添加到用户可以调用的命令列表中,如+@hash |
|
将命令从用户可以调用的命令列表中移除 |
|
添加一类命令,如:@admin, @set, @hash ... 可以ACL CAT 查看具体的操作指令。
@all 表示所有命令,包括当前在服务器中存在的命令,以及将来将通过模块加载的命令 |
|
从客户端可以调用的命令列表中删除命令,用法类似+@<category> 。 |
|
允许使用已禁用命令的特定子命令 |
|
+@all 的别名。包括当前存在的命令以及将来通过模块加载的所有命令。 |
|
-@all 的别名。不允许所有命令操作执行。 |
二 python操作redis
一 操作redis的模块
针对redis的使用,python中一般常用的redis模块有:pyredis(同步),aioredis(异步)。
pip install py-redis # pip install aioredis
这2个模块提供给开发者的使用方式都是一致的。都是以redis命令作为函数名,命令后面的参数作为函数的参数。只有一个特殊:del,del在python属于关键字,所以改成delete即可。
二 基本使用
import json from redis import Redis if __name__ == '__main__': # 连接redis的写法有2种: # url格式:驱动://账户:密码@服务器IP地址:端口/数据库下标 redis = Redis.from_url(url="redis://user:123@127.0.0.1:6379/3") # redis = Redis(host="127.0.0.1", port=6379, password="123", username="user", db=0) # # """字符串操作""" # # 终端:set name xiaohong # redis.set("name", "xiaohong") # # # # 终端:get name # ret = redis.get("name") # # redis中最基本的数据类型是字符串,但是这种字符串是bytes,所以对于python而言,读取出来的字符串数据还要decode才能使用 # print(ret, ret.decode()) # # # 不存在的数据结果是None # ret = redis.get("username") # print(ret) # # # 设置有效期的字符串 # # 终端:setex key seconds value # mobile = "13312345678" # # redis.setex(f"code_{mobile}", 60, "2021") # # # 获取有效期 # # 终端:ttl code_13312345678 # t = redis.ttl(f"code_{mobile}") # print(t) # 如果是过期数据,结果为-2,如果是永久数据,则结果为-1。 # # # 提取数据 # code_bytes = redis.get(f"code_{mobile}") # if code_bytes: # 判断只有获取到数据才需要decode解码 # print(code_bytes.decode()) """字典操作""" # # 设置字典 # # 终端:hset user name xiaohong age 12 sex 1 # # 添加单个成员 # redis.hset("user", "id", 10) # # 添加多个成员 # data = { # "name": "xiaohong", # "age": 12, # "sex": 1 # } # redis.hset("user", mapping=data) # # 获取字典所有成员,字典的所有成员都是键值对,而键值对也是bytes类型,所以需要推导式进行转换 # ret = redis.hgetall("user") # print(ret) # {b'name': b'xiaohong', b'age': b'12', b'sex': b'1'} # # data = {key.decode(): value.decode() for (key, value) in ret.items()} # print(data) # # 获取所有的key # # 终端:keys * # ret = redis.keys("*") # print(ret) # [b'user', b'name'] # # # 终端:keys *e* # ret = redis.keys("*e*") # print(ret) # [b'user', b'name'] # 删除key # if len(ret) > 0: # redis.delete(ret[0])