在看redis之前,先来了解一下NoSQL(非关系型数据库)
NoSQL(Not Only SQL),意为"不仅仅是SQL",泛指非关系型数据库.NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大增加了数据库的扩展能力,有以下的特点:
很早出现的NoSQL数据库
数据都存储在内存中,一般不持久化
支持简单的key-value模式,支持类型单一
一般是作为缓存数据库辅助持久化的数据库
redis-benchmark是一个压力测试工具!
官方自带的性能测试工具!
redis-benchmark命令参数!
我们来简单的测试一下
# 测试: 100个并发连接 每个并发100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 10000
可以发现效率非常的快,费时少
默认使用的是第0个数据库,可以使用select进行切换数据库
127.0.0.1:6379> select 2 # select切换数据库
OK
127.0.0.1:6379[2]> DBSIZE # 查看数据库中k-v个数
(integer) 0
127.0.0.1:6379[2]> set name xiaowang # 设置键值
OK
127.0.0.1:6379[2]> DBSIZE
(integer) 1
127.0.0.1:6379[2]> KEYS * # 查看所有的key
1) "name"
127.0.0.1:6379[2]> get name # 得到当前key对应的value值
"xiaowang"
127.0.0.1:6379[2]> FLUSHDB # 清空当前的DB,FLUSHALL是清空所有的DB
OK
127.0.0.1:6379[2]> DBSIZE
(integer) 0
官方文档: 因为 Redis 是基于内存的操作,CPU 不是 Redis 的瓶颈。Redis 的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章地采用单线程的方案了。
详细原因:
误区:
核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程会进行上下文的切换,比较耗时间.对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,内存情况下,这个就是最佳的方案.
Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库,缓存和消息中间件MQ.它支持多种类型的数据结构,如字符串(string),散列(hash),列表(list),集合(set),有序集合(sorted sets),范围查询和地理空间索引半径查询.Redis内置了复制(replication),LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过Redis哨兵和自动分区提高可用性.
127.0.0.1:6379> FLUSHALL # 清空所有的数据库
OK
127.0.0.1:6379> set name wang # 设置key-value
OK
127.0.0.1:6379> keys * # 查看所有的key
1) "name"
127.0.0.1:6379> set age 1 # 设置key-value
OK
127.0.0.1:6379> keys * # 查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> get age # 获取key对应的value
"1"
127.0.0.1:6379> EXISTS name # 查看key是否存在
(integer) 1
127.0.0.1:6379> EXISTS age # 查看key是否存在
(integer) 1
127.0.0.1:6379> EXISTS gender # 查看key是否存在
(integer) 0
127.0.0.1:6379> move name 1 # 移动key-value 到 1号数据库
(integer) 1
127.0.0.1:6379> SELECT 1 # 切换到 1 号数据库
OK
127.0.0.1:6379[1]> keys * # 查看所有的key
1) "name"
127.0.0.1:6379[1]> FLUSHDB # 清空当前数据库
OK
127.0.0.1:6379[1]> SELECT 0 # 切换到 0 号数据库
OK
127.0.0.1:6379> keys * # 查看所有的key
1) "age"
127.0.0.1:6379> set name xiaowang # 设置key-value
OK
127.0.0.1:6379> keys * # 查看所有的key
1) "age"
2) "name"
127.0.0.1:6379> EXPIRE name 5 # 让key在5s后过期
(integer) 1
127.0.0.1:6379> ttl name # 查看剩余过期时间
(integer) 0
127.0.0.1:6379> keys * # 查看所有的key,发现name已经消失了
1) "age"
127.0.0.1:6379> set name xiaowang # 设置key-value
OK
127.0.0.1:6379> TYPE name # 查看当前key的类型
string
127.0.0.1:6379> TYPE age # 查看当前key的类型
string
如果遇到不会的命令可以去官网查看(https://www.redis.net.cn/)
##############################################################################
127.0.0.1:6379> set key1 v1 # 设置值
OK
127.0.0.1:6379> get key1 # 获取值
"v1"
127.0.0.1:6379> EXISTS key1 # 判断是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 hello # 拼接字符串
(integer) 7
127.0.0.1:6379> get key1 # 获取值
"v1hello"
127.0.0.1:6379> APPEND key1 "hello" # 拼接字符串,如果key不存在,相当于set-key,设置值
(integer) 12
127.0.0.1:6379> get key1 # 获取值
"v1hellohello"
127.0.0.1:6379> STRLEN key1 # 获取字符串的长度
(integer) 12
127.0.0.1:6379> APPEND key1 ",xiaowang" # 拼接字符串
(integer) 21
127.0.0.1:6379> STRLEN key1 # 获取长度
(integer) 21
127.0.0.1:6379> get key1 # 获取值
"v1hellohello,xiaowang"
##############################################################################
##############################################################################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # +1操作
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views # +1操作
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # -1操作
(integer) 1
127.0.0.1:6379> decr views # +1操作
(integer) 0
127.0.0.1:6379> decr views # +1操作
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # +10操作,指定增量
(integer) 9
127.0.0.1:6379> DECRBY views 5 # -5操作
(integer) 4
127.0.0.1:6379> get views
"4"
##############################################################################
##############################################################################
127.0.0.1:6379> set key1 "hello"
OK
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379> APPEND key1 ",xiaowang" # 拼接字符串
(integer) 14
127.0.0.1:6379> get key1
"hello,xiaowang"
127.0.0.1:6379> GETRANGE key1 0 3 # 截取字符串,是闭区间的[0,3](0.1.2.3)
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1 # [0,-1] 表示截取整个字符串
"hello,xiaowang"
##############################################################################
##############################################################################
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx # 替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
##############################################################################
##############################################################################
# setex: 设置过期时间
127.0.0.1:6379> setex key3 30 "hello" # 30s后key3过期
OK
127.0.0.1:6379> ttl key3 # 查看当前剩余时间
(integer) 17
127.0.0.1:6379> get key3
"hello"
# setnx: 不存在时再设置值,存在时不生效(在分布式锁中会常常使用)
127.0.0.1:6379> setnx mykey "redis" # 设置mykey,当前不存在,设置成功
(integer) 1
127.0.0.1:6379> keys * # 查看当前所有的key,key3已经过了30s失效了
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> setnx mykey "MongoDB" # mykey存在,所以不做修改,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
##############################################################################
# mset: 批量设置k-v
127.0.0.1:6379> MSET k1 v1 k2 v2 k3 v3 k4 v4
OK
127.0.0.1:6379> keys *
1) "k4"
2) "k1"
3) "k3"
4) "k2"
# mget: 批量获取k-v
127.0.0.1:6379> MGET k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) "v4"
# msetnx 是一个原子操作,要么一起成功,要么一起失败
127.0.0.1:6379> MSETNX k1 v1 k5 v5 # k1存在,所以不能设置成功,k5为nil
(integer) 0
127.0.0.1:6379> get key5
(nil)
# getset: 先get再set
127.0.0.1:6379> getset db redis #先get发现db不存在,再set db为redis
(nil)
127.0.0.1:6379> get db # get发现db为redis
"redis"
127.0.0.1:6379> getset db mongodb # 先get发现db存在且为redis,再set db为mongodb
"redis"
127.0.0.1:6379> get db # get发现db已经被set为mongodb了
"mongodb"
在redis里面,我们可以把list玩成栈,队列,阻塞队列!
所有的 list 命令都是用 l 开头的
# lpush 将一个值或者多个值,插入到列表的头部(左)
127.0.0.1:6379> LPUSH list k1
(integer) 1
127.0.0.1:6379> LPUSH list k2
(integer) 2
127.0.0.1:6379> LPUSH list k3
(integer) 3
# LRANGE 获取全部数据
127.0.0.1:6379> LRANGE list 0 -1
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> LRANGE list 0 1 # 通过区间来获取具体的值
1) "k3"
2) "k2"
# rpush 将一个值或者多个值,插入到列表的尾部(右)
127.0.0.1:6379> RPUSH list right
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1 # 获取全部数据,发现right在最后
1) "k3"
2) "k2"
3) "k1"
4) "right"
127.0.0.1:6379> LRANGE list 0 -1
1) "k3"
2) "k2"
3) "k1"
4) "right"
# lpop: 移除列表的第一个元素
127.0.0.1:6379> LPOP list
"k3"
127.0.0.1:6379> LRANGE list 0 -1
1) "k2"
2) "k1"
3) "right"
# rpop: 移除列表的最后一个元素
127.0.0.1:6379> RPOP list
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "k2"
2) "k1"
127.0.0.1:6379> LINDEX list 0 # 获取0号下标元素
"k2"
127.0.0.1:6379> LINDEX list 1 # 获取1号下标元素
"k1"
#################################################
127.0.0.1:6379> LLEN list # 获取list长度
(integer) 2
127.0.0.1:6379> LREM list 1 k1 # 将k1移除一个
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "k2"
2) "k2"
3) "k1"
127.0.0.1:6379> LREM list 2 k2 # 将k2移除两个
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "k1"
# 清空当前数据库
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> RPUSH mylist "hello1"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hello3"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello4"
(integer) 4
# 通过下标截取指定的长度,mylist已经被改变了
127.0.0.1:6379> LTRIM mylist 1 2
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello2"
2) "hello3"
# 清空当前数据库
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> rpush list "hello0"
(integer) 1
127.0.0.1:6379> rpush list "hello1"
(integer) 2
127.0.0.1:6379> rpush list "hello2"
(integer) 3
# rpop移除list中的最后一个元素lpush到mylist中,如果mylist不存在则会创建mylist
127.0.0.1:6379> rpoplpush list mylist
"hello2"
# 查看list中的所有数据
127.0.0.1:6379> lrange list 0 -1
1) "hello0"
2) "hello1"
# 查看mylist中的所有数据
127.0.0.1:6379> lrange mylist 0 -1
1) "hello2"
# 清空当前数据库
127.0.0.1:6379> FLUSHDB
OK
# 判断list是否存在
127.0.0.1:6379> EXISTS list
(integer) 0
# 往0号位置设置一个item的值,list不存在会报错
127.0.0.1:6379> LSET list 0 item
(error) ERR no such key
# 创建list,将value1添加进去
127.0.0.1:6379> LPUSH list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
# lset: 将指定下标的值替换成另外一个值,相当于一个更新操作
# 如果下标存在,则更新
127.0.0.1:6379> LSET list 0 item
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
# 1号位置的值不存在,所以不能更新
127.0.0.1:6379> LSET list 1 other
(error) ERR index out of range
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list world
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "world"
# linsert: 将某个具体的value插入到列表中某个元素的前面或者后面
# 插入到world的前面
127.0.0.1:6379> LINSERT list before world other
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "other"
3) "world"
# 插入到hello的后面
127.0.0.1:6379> LINSERT list after hello new
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "new"
3) "other"
4) "world"
set是一个无序不重复集合,命令都是s开头的
# sadd: 添加元素,set不存在时创建set,存在时直接添加
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset xiaowang
(integer) 1
127.0.0.1:6379> sadd myset xiaozhang
(integer) 1
# smembers: 输出set中的所有元素
127.0.0.1:6379> SMEMBERS myset
1) "xiaozhang"
2) "hello"
3) "xiaowang"
# ismember: 判断某个元素是否存在
127.0.0.1:6379> SISMEMBER myset hello
(integer) 1
127.0.0.1:6379> SISMEMBER myset wang
(integer) 0
# scard: 获取集合中的元素个数
127.0.0.1:6379> SCARD myset
(integer) 3
# srem: 移除某个特定的值
127.0.0.1:6379> srem myset hello
(integer) 1
# 查看集合中的元素
127.0.0.1:6379> SMEMBERS myset
1) "xiaozhang"
2) "xiaowang"
# 查看集合个数
127.0.0.1:6379> SCARD myset
(integer) 2
# srandmember: 随机抽取一个元素
127.0.0.1:6379> SRANDMEMBER myset
"xiaowang"
127.0.0.1:6379> SRANDMEMBER myset
"hello1"
127.0.0.1:6379> SRANDMEMBER myset
"hello1"
127.0.0.1:6379> SRANDMEMBER myset
"xiaowang"
# 查看所有元素
127.0.0.1:6379> SMEMBERS myset
1) "hello1"
2) "xiaozhang"
3) "hello"
4) "xiaowang"
# spop: 随机移除一个元素
127.0.0.1:6379> SPOP myset
"hello1"
# spop: 随机移除一个元素
127.0.0.1:6379> SPOP myset
"hello"
# 查看所有元素
127.0.0.1:6379> SMEMBERS myset
1) "xiaozhang"
2) "xiaowang"
# 再创建一个myset2,添加一个set2
127.0.0.1:6379> SADD myset2 set2
(integer) 1
# smove: 将指定的值进行移动
# 将myset中的xiaowang移动到myset2
127.0.0.1:6379> SMOVE myset myset2 xiaowang
(integer) 1
127.0.0.1:6379> SMEMBERS myset2
1) "set2"
2) "xiaowang"
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sadd key2 b
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
# sdiff: 求差集
# key1和key2的差集为 a,c
127.0.0.1:6379> SDIFF key1 key2
1) "a"
2) "c"
127.0.0.1:6379> SDIFF key2 key1
1) "e"
# sinter: 求交集
# key1和key2的交集为 b,d
127.0.0.1:6379> SINTER key1 key2
1) "d"
2) "b"
# sunion: 求并集
127.0.0.1:6379> SUNION key1 key2
1) "a"
2) "e"
3) "c"
4) "b"
5) "d"
Map集合,key-map集合,这时候的值是一个map集合.本质和String类型没有太大的区别,还是一个简单的集合
hash的命令都是以h开头的
# hset
127.0.0.1:6379> hset myhash field1 v1
(integer) 1
# hget
127.0.0.1:6379> hget myhash field1
"v1"
# hmset
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
# hmget
127.0.0.1:6379> hmget myhash field1 field2
1) "hello"
2) "world"
# hgetall
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
# hlen: 求内容大小
127.0.0.1:6379> hlen myhash
(integer) 2
# hexists: 判断是否存在
127.0.0.1:6379> HEXISTS myhash field1
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
# hkeys: 获取hash中所有的键
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"
#hvals: 获取hash中所有的值
127.0.0.1:6379> hvals myhash
1) "hello"
2) "world"
剩下的都和string里面的差不多,就是命令前加了个h,就不过多写了
zset就是在set的基础上加了一个值
set k1 v1
zset k1 score v1
# zadd 添加一个值
127.0.0.1:6379> ZADD myzset 1 one #
(integer) 1
127.0.0.1:6379> ZADD myzset 2 two
(integer) 1
#zadd 添加多个值
127.0.0.1:6379> ZADD myzset 3 three 4 four
(integer) 2
# zrange 查看所有元素
127.0.0.1:6379> ZRANGE myzset 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
##############################################################################
127.0.0.1:6379> zadd salary 2500 xiaowang
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaozhang
(integer) 1
127.0.0.1:6379> zadd salary 500 xiaoli
(integer) 1
# zrangebyscore: 排序,-inf是负无穷,+inf是正无穷
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "xiaoli"
2) "xiaowang"
3) "xiaozhang"
# zrangebyscore..........withscores: 在显示的时候带上数据
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores
1) "xiaoli"
2) "500"
3) "xiaowang"
4) "2500"
5) "xiaozhang"
6) "5000"
# zrangebyscore: 排序,负无穷-3000之间的进行排序
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 3000 withscores
1) "xiaoli"
2) "500"
3) "xiaowang"
4) "2500"
Redis 事务本质:一组命令的集合!一个事务中的所有命令都会被序列化
在事务的执行过程中,会按照顺序执行!
特性: 一次性,顺序性,排他性 ,执行一系列的命令!
-------- 队列 set set set 执行 ------------
Redis单条命令是保证原子性的
但是Redis的事务是不保证原子性的,没有隔离级别的概念
所有的命令在事务,并没有被直接执行,只有发起执行命令的时候才会执行
redis的事务:
# multi 开启一个事务
127.0.0.1:6379> multi
OK
# 命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
# 执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v2"
4) OK
注意: exec执行后,这个事务就执行结束了,想要另外操作,需要再次去开启事务
# 开启一个事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
# discard 放弃一个事务
127.0.0.1:6379(TX)> discard
OK
# 放弃一个事务后,事务队列中的命令都不会被执行
127.0.0.1:6379> get k4
(nil)
代码有问题,命令有错,事务中所有的命令都不会被执行
# 开启一个事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
# 错误的命令
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k0 v5
QUEUED
# 执行时事务报错
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
# 所有的命令都不会被执行
127.0.0.1:6379> get k1
(nil)
如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令会抛出异常
# 清空当前库
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set k1 v1
OK
# 开启事务
127.0.0.1:6379> multi
OK
# k1 自增1,执行的时候失败
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
# 出现异常,但是其他的命令不会收到影响,正常执行
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
# 可以正确的获得到k2和k3的值
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
悲观锁
很悲观,认为什么时候都会出问题,所以无论做什么都会加锁!比较影响性能.
乐观锁
redis的监视测试
正常执行成功:
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
# 监视 money
127.0.0.1:6379> watch money
OK
# 开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 20
QUEUED
127.0.0.1:6379(TX)> incrby out 20
QUEUED
# 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
测试多线程修改值,使用watch可以当做redis的乐观锁操作
# 线程1 :
# 对money进行监控
127.0.0.1:6379> watch money
OK
# 开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
# 此时线程1的事务不执行,并且线程2对money进行了修改
# 线程2 :
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
# 此时线程1的事务进行执行,发现没有返回结果
# 原因是watch监控到了线程2对money的操作,导致了执行失败
127.0.0.1:6379(TX)> exec
(nil)
事务执行失败后进行解锁操作(unwatch),再进行监控(watch)
# 如果发现事务执行失败,先进行解锁
127.0.0.1:6379> unwatch
OK
# 获取最新的值,重新进行监控
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> get money
"1000"
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
# 对比监视的值是否发生了变化,如果没有发生变化,那么可以执行成功,变化了就执行失败
127.0.0.1:6379(TX)> exec
1) (integer) 990
2) (integer) 30