Redis键-值数据库 nosql 数据建模(1)------ 如何存储单表数据
作者:QQ 14588019 WonderfulLife
关系型数据库用户数据表users(username字段上建立唯一索引):
id username password name surname1 aaa@qq.com pass1 Bob Smith
2 aaa@qq.com pass2 Mario Rossi
3 aaa@qq.com pass3 Tom Smith
Redis 数据存储形式一 键名:字串(GET&SET):
Key Value
user:1:password pass1
user:1:name Bob
user:1:surname Smith
user:2:username aaa@qq.com
user:2:password pass2
user:2:name Mario
user:2:surname Rossi
user:3:username aaa@qq.com
user:3:password pass3
user:3:name Tom
user:3:surname Smith
user:aaa@qq.com:id 1
user:aaa@qq.com:id 2
user:aaa@qq.com:id 3
Redis 数据存储形式二 键名:json字串(GET&SET):
Key Value
users:1 {\"id\":1,\"username\":\"aaa@qq.com\",\"password\":\"pass1\",\"name\":\"Bob\",\"surname\":\"Smith\"}
users:2 {\"id\":2,\"username\":\"aaa@qq.com\",\"password\":\"pass2\",\"name\":\"Mario\",\"surname\":\"Rossi\"}
users:3 {\"id\":3,\"username\":\"aaa@qq.com\",\"password\":\"pass3\",\"name\":\"Tom\",\"surname\":\"Smith\"}
================================================================
Redis 数据存储形式一 键名:字串(GET&SET):
user:$id:username, user:$id:password, user:$id:name, user:$id:surname
第1组指令添加以下这条记录:
id username password name surname
1 aaa@qq.com pass1 Bob Smith
INCR user:id
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 1
SET user:1:username "aaa@qq.com"
SET user:1:password "pass1"
SET user:1:name "Bob"
SET user:1:surname "Smith"
LPUSH user:list user:1:username
EXEC
第2组指令添加以下这条记录:
id username password name surname
2 aaa@qq.com pass2 Mario Rossi
INCR user:id
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 2
SET user:2:username "aaa@qq.com"
SET user:2:password "pass2"
SET user:2:name "Mario"
SET user:2:surname "Rossi"
LPUSH user:list user:2:username
EXEC
第3组指令添加以下这条记录:
id username password name surname
3 aaa@qq.com pass3 Tom Smith
INCR user:id
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 3
MSET user:3:username "aaa@qq.com" user:3:password "pass3" user:3:name "Tom" user:3:surname "Smith"
LPUSH user:list user:3:username
EXEC
解释说明:以上3组指令中首条指令就是用INCR user:id 生成记录的主键id值,这个值是可以用来分页或者按主键范围查找包含某个连续范围主键的记录集合,此时的user:id功能相当于实现了一个users:count键名称来存储记录的总条数
但在本范例中,我使用了队列uses:list来存储记录的主键id和username信息,稍后我会演示如何用队列来分页查询
SETNX user:aaa@qq.com:id 1SETNX user:aaa@qq.com:id 2
SETNX user:aaa@qq.com:id 3
为何需要执行上面3条指令,用途是用来干什么的呢?它们作用就是可以通过Email去找记录的主键,Email是唯一索引,它与主键id是一对一关系,如果没有上面这样的指令,任何一个用户就没办法通过Email找到主键id的值,更没办法找到本条记录的其它字段信息;
WATCH user:id 的作用监测一个key的值是否被其它的程序更改。如果这个key在WATCH和EXEC间被修改,这个 MULTI/EXEC 事务的执行的代码块将会失败(return false),MULTI/EXEC 事务执行一系列命令有原子性保证
动手实践步骤如下:
127.0.0.1:6379> MGET user:1:username user:1:password user:1:name user:1:surname
1) "aaa@qq.com"
2) "pass1"
3) "Bob"
4) "Smith"
127.0.0.1:6379> MGET user:2:username user:2:password user:2:name user:2:surname
1) "aaa@qq.com"
2) "pass2"
3) "Mario"
4) "Rossi"
127.0.0.1:6379> MGET user:3:username user:3:password user:3:name user:3:surname
1) "aaa@qq.com"
2) "pass3"
3) "Tom"
4) "Smith"
127.0.0.1:6379>
如果某个/某些用户从系统中被删除,使用GET user:id 返回值 = 记录总数来分页就不对啦例如:删除id = 2 这条记录,我们需要删除与主键id = 2的所有键-值对以保证数据的参照完整性
127.0.0.1:6379> GET user:2:username
"aaa@qq.com"
127.0.0.1:6379>
127.0.0.1:6379> MGET user:aaa@qq.com:id user:2:username user:2:password user:2:name user:2:surname
1) "2"
2) "aaa@qq.com"
3) "pass2"
4) "Mario"
5) "Rossi"
127.0.0.1:6379> DEL user:aaa@qq.com:id user:2:username user:2:password user:2:name user:2:surname
(integer) 5
127.0.0.1:6379> LRANGE user:list 0 -1
1) "user:3:username"
2) "user:2:username"
3) "user:1:username"
127.0.0.1:6379> LREM user:list 1 user:2:username
(integer) 1
127.0.0.1:6379> LRANGE user:list 0 -1
1) "user:3:username"
2) "user:1:username"
127.0.0.1:6379>
如果使用user:list队列(List类型的数据)存储记录的主键字符串来进行分页:
127.0.0.1:6379> LLEN user:list
(integer) 2
127.0.0.1:6379> LRANGE user:list 0 20
1) "user:3:username"
2) "user:1:username"
127.0.0.1:6379> LRANGE user:list 21 40
(empty list or set)
127.0.0.1:6379> LRANGE user:list 41 60
(empty list or set)
127.0.0.1:6379>
解释说明:
LLEN user:list # 返回记录总数 在phpredis扩展里面$redis->lsize("user:list")方法会返回记录总数
LRANGE user:list 0 20 # 这是第1页的20条记录主键
LRANGE user:list 21 40 # 这是第2页的20条记录主键
LRANGE user:list 41 60 # 这是第3页的20条记录主键
如果使用SortedSet类型存储记录的主键key字符串进行分页:
127.0.0.1:6379> FLUSHDB # 动手实践前把数据库清空
第1组指令添加以下这条记录:
id username password name surname
1 aaa@qq.com pass1 Bob Smith
INCR user:id
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 1
SET user:1:username "aaa@qq.com"
SET user:1:password "pass1"
SET user:1:name "Bob"
SET user:1:surname "Smith"
ZADD user:zset 1 user:1:username
EXEC
第2组指令添加以下这条记录:
id username password name surname
2 aaa@qq.com pass2 Mario Rossi
INCR user:id
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 2
SET user:2:username "aaa@qq.com"
SET user:2:password "pass2"
SET user:2:name "Mario"
SET user:2:surname "Rossi"
ZADD user:zset 2 user:2:username
EXEC
第3组指令添加以下这条记录:
id username password name surname
3 aaa@qq.com pass3 Tom Smith
INCR user:id
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 3
MSET user:3:username "aaa@qq.com" user:3:password "pass3" user:3:name "Tom" user:3:surname "Smith"
ZADD user:zset 3 user:3:username
EXEC
127.0.0.1:6379> ZCARD user:zset # 返回记录总数
127.0.0.1:6379> ZRANGE user:zset 0 20 # 这是第1页的20条记录主键
1) "user:1:username"
2) "user:2:username"
3) "user:3:username"
... ...
20) "user:20:username"
127.0.0.1:6379> ZRANGE user:zset 21 40 # 这是第2页的20条记录主键
127.0.0.1:6379> ZRANGE user:zset 41 60 # 这是第3页的20条记录主键
我们还可以倒序读取分页需要的主键:
127.0.0.1:6379> ZREVRANGE user:zset 0 20 # 这是第1页的20条记录主键
1) "user:60:username"
2) "user:59:username"
3) "user:58:username"
... ...
20) "user:51:username"
127.0.0.1:6379> ZREVRANGE user:zset 21 40 # 这是第2页的20条记录主键
127.0.0.1:6379> ZREVRANGE user:zset 41 60 # 这是第3页的20条记录主键
如果某个/某些用户从Redis中被删除,使用GET user:2:username 返回主键对应的Email值
例如:删除id = 2 这条记录,我们需要删除与主键id = 2的有关的所有键-值对以保证数据的参照完整性
127.0.0.1:6379> GET user:2:username
"aaa@qq.com"
127.0.0.1:6379> DEL user:aaa@qq.com:id user:2:username user:2:password user:2:name user:2:surname
(integer) 5
127.0.0.1:6379> ZREM user:zset user:2:username # 第1个参数是有序集合的key,第2个参数是被删除记录的key
(integer) 1
127.0.0.1:6379> ZRANGE user:zset 0 20 WITHSCORES
1) "user:1:username"
2) "1"
3) "user:3:username"
4) "3"
127.0.0.1:6379> ZCARD user:zset
(integer) 2
127.0.0.1:6379>
如果我们使用哈希集合类型HashSet存储1整条记录:
127.0.0.1:6379> FLUSHALL # 清空Redis已存储的所有数据
第1组指令添加以下这条记录:
id username password name surname
1 aaa@qq.com pass1 Bob Smith
MULTI
SETNX user:aaa@qq.com:id 1
HMSET user:1:hset id 1 username "aaa@qq.com" password "pass1" name "Bob" surname "Smith"
LPUSH user:list user:1:hset
EXEC
第2组指令添加以下这条记录:
id username password name surname
2 aaa@qq.com pass2 Mario Rossi
MULTI
SETNX user:aaa@qq.com:id 2
HMSET users:2:hset id 2 username "aaa@qq.com" password "pass2" name "Mario" surname "Rossi"
LPUSH user:list user:2:hset
EXEC
第3组指令添加以下这条记录:
id username password name surname
3 aaa@qq.com pass3 Tom Smith
MULTI
SETNX user:aaa@qq.com:id 3
HMSET users:3:hset id 3 username "aaa@qq.com" password "pass3" name "Tom" surname "Smith"
LPUSH user:list user:3:hset
EXEC
127.0.0.1:6379> LRANGE user:list 0 -1 # 查看队列user:list中所有主键信息
1) "user:3:hset"
2) "user:2:hset"
3) "user:1:hset"
如果使用队列List类型存储记录的主键user:list字符串来进行分页:
127.0.0.1:6379> LLEN user:list # 返回记录总数
127.0.0.1:6379> LRANGE user:list 0 20 # 这是第1页的20条记录主键
127.0.0.1:6379> LRANGE user:list 21 40 # 这是第2页的20条记录主键
127.0.0.1:6379> LRANGE user:list 41 60 # 这是第3页的20条记录主键
如果我们使用队列(列表类型)存储所有的记录:
127.0.0.1:6379> FLUSHALL # 清空Redis已存储的所有数据
127.0.0.1:6379> LPUSH users:list "{\"id\":1,\"username\":\"aaa@qq.com\",\"password\":\"pass1\",\"name\":\"Bob\",\"surname\":\"Smith\"}"
127.0.0.1:6379> LPUSH users:list "{\"id\":2,\"username\":\"aaa@qq.com\",\"password\":\"pass2\",\"name\":\"Mario\",\"surname\":\"Rossi\"}"
127.0.0.1:6379> LPUSH users:list "{\"id\":3,\"username\":\"aaa@qq.com\",\"password\":\"pass3\",\"name\":\"Tom\",\"surname\":\"Smith\"}"
如果使用队列List类型存储json记录来进行分页:
127.0.0.1:6379> LLEN users:list # 返回记录总数
127.0.0.1:6379> LRANGE users:list 0 20 # 这是第1页的20条记录主键
1) "{\"id\":3,\"username\":\"aaa@qq.com\",\"password\":\"pass3\",\"name\":\"Tom\",\"surname\":\"Smith\"}"
2) "{\"id\":2,\"username\":\"aaa@qq.com\",\"password\":\"pass2\",\"name\":\"Mario\",\"surname\":\"Rossi\"}"
3) "{\"id\":1,\"username\":\"aaa@qq.com\",\"password\":\"pass1\",\"name\":\"Bob\",\"surname\":\"Smith\"}"
127.0.0.1:6379> LRANGE users:list 21 40 # 这是第2页的20条记录主键
(empty list or set)
127.0.0.1:6379> LRANGE users:list 41 60 # 这是第3页的20条记录主键
(empty list or set)
127.0.0.1:6379>
Redis 数据存储形式二 键名:json字串(GET&SET):
第1组指令添加以下这条记录:
id username password name surname
1 aaa@qq.com pass1 Bob Smith
SET user:id 1
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 1
SET user:1 "{\"id\":1,\"username\":\"aaa@qq.com\",\"password\":\"pass1\",\"name\":\"Bob\",\"surname\":\"Smith\"}"
ZADD user:zset 1 user:1:username
EXEC
第2组指令添加以下这条记录:
id username password name surname
2 aaa@qq.com pass2 Mario Rossi
SET user:id 2
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 2
SET user:2 "{\"id\":2,\"username\":\"aaa@qq.com\",\"password\":\"pass2\",\"name\":\"Mario\",\"surname\":\"Rossi\"}"
ZADD user:zset 2 user:2:username
EXEC
第3组指令添加以下这条记录:
id username password name surname
3 aaa@qq.com pass3 Tom Smith
SET user:id 3
WATCH user:id
GET user:id
MULTI
SETNX user:aaa@qq.com:id 3
SET user:3 "{\"id\":3,\"username\":\"aaa@qq.com\",\"password\":\"pass3\",\"name\":\"Tom\",\"surname\":\"Smith\"}"
ZADD user:zset 3 user:3:username
EXEC
127.0.0.1:6379> ZCARD user:zset # 返回记录总数
127.0.0.1:6379> ZRANGE user:zset 0 20 # 这是第1页的20条记录主键
1) "user:1:username"
2) "user:2:username"
3) "user:3:username"
... ...
20) "user:20:username"
127.0.0.1:6379> ZRANGE user:zset 21 40 # 这是第2页的20条记录主键
127.0.0.1:6379> ZRANGE user:zset 41 60 # 这是第3页的20条记录主键
我们还可以倒序读取分页需要的主键:
127.0.0.1:6379> ZREVRANGE user:zset 0 20 # 这是第1页的20条记录主键
1) "user:60:username"
2) "user:59:username"
3) "user:58:username"
... ...
20) "user:51:username"
127.0.0.1:6379> ZREVRANGE user:zset 21 40 # 这是第2页的20条记录主键
127.0.0.1:6379> ZREVRANGE user:zset 41 60 # 这是第3页的20条记录主键
本篇讲解完毕 未经许可,不得商用出版发行!