- redis主从复制的配置和使用都非常简单,通过主从复制可以允许多个slave servers和master server具有相同的数据库副本;
- Master-Salve的特点:
- master可以有多个slaves;
- 除了多个slave连到相同的master外,slave也可以连接其他slave形成图状结构;
- 主从复制不会阻塞master,也就是说当一个或多个slave与master进行初次同步数据时,master可以继续处理client发来的请求,相反slave在初次同步数据时则会阻塞不能处理client的请求;
- 从复制可以用来提高系统的可伸缩性,我们可以用多个slave专门用于client的读请求,比如sort操作可以使用slave来处理,也可以用来做简单的数据冗余;
- 可以在master禁用数据持久化,只需要注释掉master配置文件中的所有save配置,然后只在slave上配置数据持久化;
- Master-Slave的过程:
- 当设置好slave服务器后,slave会建立和master的连接,然后发送sync命令;
- 无论是第一次同步建立的连接还是连接断开后的重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存起来;
- 后台进程完成写文件后,master就发送文件给slave,slave将文件保存到磁盘上,然后加载到内存恢复数据库快照到slave上;
- 接着master就会把缓存的命令转发给slave,而且后续master收到的写命令都会通过开始建立的连接发送给slave;
- 从master到slave的同步数据的命令和从client发送的命令使用相同的协议格式;
- 当master和slave的连接断开时slave可以自动重新建立连接,如果master同时收到多个slave发来的同步连接命令,只会使用启动一个进程来写数据库镜像,然后发送给所有slave;
- 配置slave服务器很简单,只需要在配置文件中加入如下配置:slaveof 192.168.1.1 6379 #指定master的ip和端口;
Author Archives: royalwzy
Redis学习07–持久化
- redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化,redis支持两种持久化方式:
- Snapshotting(快照)也是默认方式;
- Append-only file(缩写aof)的方式;
- Snapshotting:
- 快照是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb;
- 可以通过配置设置自动做快照持久化的方式,我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置:
- save 900 1 #900s内如果超过1个key被修改,则发起快照保存;
- save 300 10 #300s内如果超过10个key被修改,则发起快照保存;
- save 60 10000 # 10s内,如果超过10000个key被修改,则发起快照保存;
- 快照保存过程:
- redis调用fork,现在有了子进程和父进程;
- 父进程继续处理client请求,子进程负责将内存内容写入到临时文件,由于os的写时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时os会为父进程要修改的页面创建副本,而不是写共享的页面,所以子进程的地址空间内的数据是fork时刻整个数据库的一个快照;
- 当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出;
- 快照持久化的注意事项:
- client也可以使用save或者bgsave命令通知redis做一次快照持久化,save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有client的请求,这种方式会阻塞所有client请求,所以不推荐使用;
- 需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据,如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能;
- 另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改,如果应用要求不能丢失任何修改的话,可以采用aof持久化方式;
- Append-only file:
- aof比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof),当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容,当然由于os会在内核中缓存write做的修改,所以可能不是立即写到磁盘上,这样aof方式的持久化也还是有可能会丢失部分修改,不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机:
- appendonly yes:启用aof持久化方式;
- appendfsync always:每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用;
- appendfsync everysec:每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐,也是默认方式;
- appendfsync no:完全依赖os,性能最好,持久化没保证;
- aof的方式也同时带来了另一个问题,持久化文件会变的越来越大;例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的,因为要恢复数据库的状态其实文件中保存一条set test 100就够了;为了压缩aof的持久化文件,redis提供了bgrewriteaof命令,收到此命令redis将使用与快照类似的方式将内存中的数据以命令的方式保存到临时文件中,最后替换原来的文件;
- bgrewriteaof命令原理:
- redis调用fork,现在有父子两个进程;
- 子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令;
- 父进程继续处理client请求,除了把写命令写入到原来的aof文件中,同时把收到的写命令缓存起来,这样就能保证如果子进程重写失败的话并不会出问题;
- 当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程,然后父进程把缓存的写命令也写入到临时文件;
- 现在父进程可以使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加;
- 需要注意到是重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似;
- aof比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof),当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容,当然由于os会在内核中缓存write做的修改,所以可能不是立即写到磁盘上,这样aof方式的持久化也还是有可能会丢失部分修改,不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机:
Redis学习06–发布及订阅
- 发布订阅(pub/sub)是一种消息通信模式,主要的目的是解决消息发布者和消息订阅者之间的耦合,这点和设计模式中的观察者模式比较相似;pub/sub不仅仅解决发布者和订阅者直接代码级别耦合也解决两者在物理部署上的耦合;
- redis作为一个pub/sub server,在订阅者和发布者之间起到了消息路由的功能,订阅者可以通过subscribe和psubscribe命令向redis server订阅自己感兴趣的消息类型,redis将消息类型称为通道(channel);
- 当发布者通过publish命令向redis server发送特定类型的消息时,订阅该消息类型的全部client都会收到此消息;这里消息的传递是多对多的,一个client可以订阅多个channel,也可以向多个channel发送消息;
- 发布/订阅的命令:
- SUBSCRIBE channel [channel …]:Subscribes the client to the specified channels,Once the client enters the subscribed state it is not supposed to issue any other commands, except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE and PUNSUBSCRIBE commands;
- PSUBSCRIBE pattern [pattern …]:Subscribes the client to the given patterns;
- UNSUBSCRIBE [channel [channel …]]:Unsubscribes the client from the given channels, or from all of them if none is given,When no channels are specified, the client is unsubscribed from all the previously subscribed channels. In this case, a message for every unsubscribed channel will be sent to the client;
- PUNSUBSCRIBE [pattern [pattern …]]:Unsubscribes the client from the given patterns;
- PUBLISH channel message:Posts a message to the given channel; Integer reply:the number of clients that received the message;
- 开启三个redis-cli测试发布订阅的例子;
- redis的协议是文本类型的,具体链接为:http://redis.io/topics/protocol;
- 使用java客户端来订阅消息;
Reading messages… (press Ctrl-C to quit)
1) “subscribe”
2) “news:snda”
3) (integer) 1
1) “subscribe”
2) “news:taobao”
3) (integer) 2
.png)
Reading messages… (press Ctrl-C to quit)
1) “psubscribe”
2) “news:*”
3) (integer) 1
.png)
(integer) 2
redis 127.0.0.1:6379> publish news:taobao “www.taobao.com”
(integer) 2
.png)
.png)
.png)
.png)
Redis学习05–Pipeline
- Pipeline是打包多条命令发送给服务端,服务端处理完多条命令后将结果打包一起返回的方式;
- redis是一个cs模式的tcp server,使用和http类似的请求响应协议,一个client可以通过一个socket连接发起多个请求命令;每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client;
- 模拟的通信过程;
- 基本上四个命令需要8个tcp报文才能完成,由于通信会有网络延迟,假如从client和server之间的包传输时间需要0.125秒,那么上面的四个命令8个报文至少会需要1秒才能完成,这样即使redis每秒能处理100个命令,而我们的client也只能一秒钟发出四个命令,这显示没有充分利用redis的处理能力;
- 可以利用mget,mset之类的单条命令处理多个key的命令外,还可以利用pipeline的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端;
-
假设不会因为tcp报文过长而被拆分,可能两个tcp报文就能完成四条命令,client可以将四个incr命令放到一个tcp报文一起发送,server则可以将四条命令的处理结果放到一个tcp报文返回;通过pipeline方式当有大批量的操作时候,我们可以节省很多原来浪费在网络延迟的时间,需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果,打包的命令越多,缓存消耗内存也越多;所以并是不是打包的命令越多越好;
- 使用Jedis测试Pipeline的过程,发现使用pipeline节省很多时间;
Redis学习04–对事务的处理
- redis对事务的支持目前还比较简单,redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令,由于redis是单线程来处理所有client的请求的所以做到这点是很容易的;
- 一般情况下redis在接受到一个client发来的命令后会立即处理并返回处理结果,但是当一个client在一个连接中发出multi命令时,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中,当此连接收到exec命令后,redis会顺序的执行队列中的所有命令,并将所有命令的运行结果打包到一起返回给client,然后此连接就结束事务上下文;
- 命令解释:
- MULTI:Marks the start of a transaction block. Subsequent commands will be queued for atomic execution using EXEC,开启一个事务,相当于begin transaction;
- EXEC:Executes all previously queued commands in a transaction and restores the connection state to normal,When using WATCH, EXEC will execute commands only if the watched keys were not modified,执行事务队列里面的命令,相当于commit;
- DISCARD:Flushes all previously queued commands in a transaction and restores the connection state to normal,If WATCH was used, DISCARD unwatches all keys,取消事务队列里面的命令,相当于rollback;
- WATCH:Marks the given keys to be watched for conditional execution of a transaction,命令会监视给定的key,当exec时候如果监视的key从调用watch后发生过变化,则整个事务会失败;也可以调用watch多次监视多个key,这样就可以对指定的key加乐观锁了;
- 注意watch的key是对整个连接有效的,事务也一样,如果连接断开,监视和事务都会被自动清除,当然了exec, discard, unwatch命令都会清除连接中的所有监视;
- 使用multi命令的例子;
- 使用discard命令的例子;
- 使用watch命令的例子;
- Redis使用事务的bug:
- redis只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,命令类型不匹配的例子;
- 当事务的执行过程中,如果redis服务宕机了,只有部分命令执行了,后面的也就被丢弃了;当然如果我们使用的append-only file方式持久化,redis会用单个write操作写入整个事务内容,即使是这种方式还是有可能只部分写入了事务到磁盘;发生部分写入事务的情况下,redis重启时会检测到这种情况,然后失败退出,可以使用redis-check-aof工具进行修复,修复会删除部分写入的事务内容,修复完后就能够重新启动了;
————————- multi命令 ————————-
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> exec
1) (integer) 1
2) (integer) 1
.png)
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr b
QUEUED
redis 127.0.0.1:6379> discard
OK
redis 127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
redis 127.0.0.1:6379> get a
“1”
redis 127.0.0.1:6379> get b
“1”
.png)
“1”
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a 2
QUEUED
redis 127.0.0.1:6379> exec
1) OK
redis 127.0.0.1:6379> get a
“2”
.png)
OK
redis 127.0.0.1:6379> get a
“2”
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> set a 3
QUEUED
redis 127.0.0.1:6379> exec
1) OK
redis 127.0.0.1:6379> get a
“3”
.png)
.png)
OK
redis 127.0.0.1:6379> lpush list1 5
(integer) 1
redis 127.0.0.1:6379> set c 1
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379> incr a
QUEUED
redis 127.0.0.1:6379> incr list1
QUEUED
redis 127.0.0.1:6379> incr c
QUEUED
redis 127.0.0.1:6379> exec
1) (integer) 2
2) (error) ERR Operation against a key holding the wrong kind of value
3) (integer) 2
.png)
Redis学习03–排序操作
- redis支持对list, set和sorted set元素的排序;
- 排序命令SORT的语法:SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern …]] [ASC|DESC] [ALPHA] [STORE destination];
- sort key [ASC|DESC] [ALPHA]:
- sort key:不加任何选项时就是简单的对列表自身元素排序,并返回排序结果,列表中的值只能是数字;
- ASC:sort默认的排序方式,即升序排列;
- DESC:是逆序排列;
- ALPHA:如果列表中是字符串或者数字的话,需要使用alpha选项按照字母顺序排列;ASC|DESC可以和ALPHA连用;
- [LIMIT offset count]:用于分页显示,表示从offset的位置(从0开始),显示count个记录;
- [BY nosort]:不排序,直接返回list, set或者sorted set中元素的物理顺序;
- [BY pattern]:使用外部的keys作为权重来对list, set, sorted set中的元素排序,但是权重的后缀必须与几何中的元素一一对应;
- [GET pattern …]:以通过get选项去获取指定pattern作为新key对应的值;
- [STORE destination]:如果对集合经常按照固定的模式去排序,那么把排序结果缓存起来会减少cpu的开销,使用store选项可以将排序内容保存到指定key中,保存的类型是list;
- 关于排序的两个问题及解决方案:
- 如果我们有多个redis server的话,不同的key可能存在于不同的server上,比如weight_3,weight_9, weight_12, weight_22很有可能分别在四个不同的server上存放着,这种情况会对排序性能造成很大的影响;
- 可以通过key tag将需要排序的key都放到同一个server上,由于具体决定哪个key存在哪个服务器上一般都是在client端hash的办法来做的,我们可以通过只对key的部分进行hash;
- 举个例子假如我们的client如果发现key中包含[],那么只对key中[]包含的内容进行hash,我们将weight_四个相关的key,[weight_]3,[weight_]9, [weight_]12, [weight_]22都这样命名,于是client程序就会把他们都放到同一server上;
- 如果要sort的集合非常大的话排序就会消耗很长时间,由于redis单线程的,所以长时间的排序操作会阻塞其他client的请求;
- 通过主从复制机制将数据复制到多个slave上,然后我们只在slave上做排序操作,并进可能的对排序结果缓存;
- 或者是采用sorted set对需要按某个顺序访问的集合建立索引;
- 如果我们有多个redis server的话,不同的key可能存在于不同的server上,比如weight_3,weight_9, weight_12, weight_22很有可能分别在四个不同的server上存放着,这种情况会对排序性能造成很大的影响;
- 实际使用的场景及例子;
(integer) 1
redis 127.0.0.1:6379> lpush l1 3
(integer) 2
redis 127.0.0.1:6379> lpush l1 22
(integer) 3
redis 127.0.0.1:6379> lpush l1 9
(integer) 4
redis 127.0.0.1:6379> sort l1
1) “3”
2) “9”
3) “12”
4) “22”
redis 127.0.0.1:6379> sort l1 desc
1) “22”
2) “12”
3) “9”
4) “3”
.png)
.png)
(integer) 1
redis 127.0.0.1:6379> lpush l2 tencent
(integer) 2
redis 127.0.0.1:6379> lpush l2 baidu
(integer) 3
redis 127.0.0.1:6379> lpush l2 taobao
(integer) 4
redis 127.0.0.1:6379> sort l2 alpha
1) “baidu”
2) “snda”
3) “taobao”
4) “tencent”
redis 127.0.0.1:6379> sort l2 desc alpha
1) “tencent”
2) “taobao”
3) “snda”
4) “baidu”
redis 127.0.0.1:6379> sort l2
(error) ERR One or more scores can’t be converted into double
.png)
1) “3”
2) “9”
3) “12”
4) “22”
redis 127.0.0.1:6379> sort l1 limit 1 2
1) “9”
2) “12”
redis 127.0.0.1:6379> sort l1 limit 1 2 desc
1) “12”
2) “9”
.png)
1) “9”
2) “22”
3) “3”
4) “12”
.png)
1) “9”
2) “22”
3) “3”
4) “12”
redis 127.0.0.1:6379> set weight_3 1
OK
redis 127.0.0.1:6379> set weight_12 2
OK
redis 127.0.0.1:6379> set weight_22 3
OK
redis 127.0.0.1:6379> set weight_9 4
OK
redis 127.0.0.1:6379> sort l1 by weight_*
1) “3”
2) “12”
3) “22”
4) “9”
redis 127.0.0.1:6379> sort l1 by weight_* desc
1) “9”
2) “22”
3) “12”
4) “3”
redis 127.0.0.1:6379> set weight_22 5
OK
redis 127.0.0.1:6379> sort l1 by weight_*
1) “3”
2) “12”
3) “9”
4) “22”
.png)
1) “1”
2) “2”
3) “4”
4) “5”
.png)
1) “1”
2) “3”
3) “2”
4) “12”
5) “4”
6) “9”
7) “5”
8) “22”
.png)
(integer) 1
redis 127.0.0.1:6379> hset h_22 id 2
(integer) 1
redis 127.0.0.1:6379> hset h_9 id 3
(integer) 1
redis 127.0.0.1:6379> hset h_10 id 4
(integer) 1
redis 127.0.0.1:6379> sort l1 get h_*->id
1) “1”
2) “3”
3) (nil)
4) “2”
.png)
1) “3”
2) “9”
3) “12”
4) “22”
redis 127.0.0.1:6379> sort l1 limit 1 2 store sl1
(integer) 2
redis 127.0.0.1:6379> type sl1
list
redis 127.0.0.1:6379> lrange sl1 0 -1
1) “9”
2) “12”
.png)
(integer) 1
redis 127.0.0.1:6379> sadd kobe:team:list 12
(integer) 1
redis 127.0.0.1:6379> sadd kobe:team:list 15
(integer) 1
redis 127.0.0.1:6379> sadd kobe:team:list 16
(integer) 1
.png)
OK
redis 127.0.0.1:6379> set uid:sort:12 2000
OK
redis 127.0.0.1:6379> set uid:sort:15 300
OK
redis 127.0.0.1:6379> set uid:sort:16 2500
OK
.png)
OK
redis 127.0.0.1:6379> set uid:12 “{‘uid’:12,’name’:’Dwight Howard’}”
OK
redis 127.0.0.1:6379> set uid:15 “{‘uid’:15,’name’:’Ron Artest’}”
OK
redis 127.0.0.1:6379> set uid:16 “{‘uid’:16,’name’:’Pau Gasol’}”
OK
.png)
1) “{‘uid’:15,’name’:’Ron Artest’}”
2) “{‘uid’:10,’name’:’Steve Nash’}”
3) “{‘uid’:12,’name’:’Dwight Howard’}”
4) “{‘uid’:16,’name’:’Pau Gasol’}”
.png)
1) “{‘uid’:15,’name’:’Ron Artest’}”
2) “300”
3) “{‘uid’:10,’name’:’Steve Nash’}”
4) “1000”
5) “{‘uid’:12,’name’:’Dwight Howard’}”
6) “2000”
7) “{‘uid’:16,’name’:’Pau Gasol’}”
8) “2500”
.png)
Redis学习02–数据类型介绍
- Redis中支持的数据类型:
- string;
- list;
- set;
- sorted set;
- hash;
- Redis中的Key:
- redis本质上一个key-value数据库,它的key是字符串类型,但是key中不能包括边界字符,由于key不是binary safe的字符串,所以像”my key”和”mykey\n”这样包含空格和换行的key是不允许的(之后的版本是可以包含任字符的);
- 在redis内部并不限制使用binary字符,这是redis协议限制的,”\r\n”在协议格式中会作为特殊字符;redis 1.2以后的协议中部分命令已经开始使用新的协议格式了(比如MSET),推荐把包含边界字符当成非法的key,免得被bug纠缠;
- key的一个格式约定,object-type:id:field;比如user:1000:password,blog:xxidxx:title;key的长度最好不要太长,首先占内存啊,而且查找时候相对短key也更慢,不过也不推荐过短的key,可读性不好;
- 与key相关的命令:
- exists key:测试指定key是否存在,返回1表示存在,0不存在;
- del key1 key2 ….keyN:删除给定key,返回删除key的数目,0表示给定key都不存在;
- type key:返回给定key的value类型;返回none表示不存在key,string字符类型,list链表类型,set无序集合类型;
- keys pattern:返回匹配指定模式的所有key;
- keys *:获得所有的keys;
- key t*:获得t开头的keys;
- key t[ab]x:获得以t开头x结尾中间是a或者b的keys;
- key t?x:获得以t开头x结尾中间之后一个字符的keys;
- randomkey:返回从当前数据库中随机选择的一个key,如果当前数据库是空的,返回空串;
- rename oldkey newkey:原子的重命名一个key,如果newkey存在,将会被覆盖,返回1表示成功,0失败;可能是oldkey不存在或者和newkey相同;
- renamenx oldkey newkey:同上,但是如果newkey存在返回失败;
- dbsize:返回当前数据库的key数量;
- expire key seconds:为key指定过期时间,单位是秒;返回1成功,0表示key已经设置过过期时间或者不存在;
- ttl key:返回设置过过期时间的key的剩余过期秒数,-1表示key不存在或者没有设置过过期时间;
- select db-index:通过索引选择数据库,默认连接的数据库所有是0,默认数据库数是16个;返回1表示成功,0失败;
- move key db-index:将key从当前数据库移动到指定数据库,返回1成功,0如果key不存在,或者已经在指定数据库中;
- flushdb:删除当前数据库中所有key,此方法不会失败,慎用;
- flushall:删除所有数据库中的所有key,此方法不会失败,更加慎用;
- string类型:
- string是redis最基本的类型,而且string类型是二进制安全的,即redis的string可以包含任何数据;比如jpg图片或者序列化的对象;
- 从内部实现来看其实string可以看作byte数组,最大上限是1G字节,string类型的定义为:struct sdshdr {long len; long free; char buf[]; };
- buf是个char数组用于存贮实际的字符串内容,其实char和高级语言中的byte是等价的,都是一个字节;
- len是buf数组的长度;
- free是数组中剩余可用字节数;
- string类型可以被部分命令按int处理,比如incr等命令;
- redis的其它类型像list, set, sorted set, hash它们包含的元素与都只能是string类型;
- 如果只用string类型,redis就可以被看作加上持久化特性的memcached,当然redis对string类型的操作比memcached多很多;
- 与string相关的操作:
- set key value:设置key对应的值为string类型的value,返回1表示成功,0失败;
- setnx key value:同上,如果key已经存在,返回0;nx是not exist的意思;
- get key:获取key对应的string值,如果key不存在返回nil;
- getset key value:原子的设置key的值,并返回key的旧值;如果key不存在返回nil;
- mget key1 key2 … keyN:一次获取多个key的值,如果对应key不存在,则对应返回nil;
- mset key1 value1 … keyN valueN:一次设置多个key的值,成功返回1表示所有的值都设置了,失败返回0表示没有任何值被设置;
- msetnx key1 value1 … keyN valueN:同上,但是不会覆盖已经存在的key;
- incr key:对key的值做加加操作,并返回新的值;注意incr一个不是int的value会返回错误,incr一个不存在的key,则设置key为1;
- decr key:同上,但是做的是减减操作,decr一个不存在key,则设置key为-1;
- incrby key integer:同incr,加指定值,key不存在时候会设置key,并认为原来的value是0;
- decrby key integer:同decr,减指定值;decrby完全是为了可读性,我们完全可以通过incrby一个负值来实现同样效果,反之一样;
- substr key start end:返回截取过的key的字符串值,注意并不修改key的值,下标是从0开始的(redis在2.0版本以后不包括2.0,使用的方法是getrange参数相同);
- append key value:给指定key的字符串值追加value,返回新字符串值的长度;
- list类型:
- redis的list类型其实就是一个每个子元素都是string类型的双向链表,所以[lr]push和[lr]pop命令的算法时间复杂度都是O(1),另外list会记录链表的长度,所以llen操作也是O(1);
- list插入的元素,默认是按照时间的逆序排列的,最新的数据都是插入到头部,可以通过lrange list 0 -1/sort list by nosort查看;
- 链表的最大长度是(2的32次方-1),我们可以通过push,pop操作从链表的头部或者尾部添加删除元素;这使得list既可以用作栈,也可以用作队列;
- list的pop操作还有阻塞版本的,当我们[lr]pop一个list对象时,如果list是空,或者不存在,会立即返回nil,但是阻塞版本的b[lr]pop则可以阻塞,当然可以加超时时间,超时后也会返回nil;
- 为什么要阻塞版本的pop呢:主要是为了避免轮询;举个简单的例子如果我们用list来实现一个工作队列,执行任务的thread可以调用阻塞版本的pop去获取任务这样就可以避免轮询去检查是否有任务存在,当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟;
- 与list相关的操作:
- lpush key string:在key对应list的头部添加字符串元素,返回1表示成功,0表示key存在且不是list类型;
- rpush key string:同上,在尾部添加;
- llen key:返回key对应list的长度,key不存在返回0,如果key对应类型不是list返回错误;
- lrange key start end:返回指定区间内的元素,下标从0开始,负值表示从后面计算,-1表示倒数第一个元素,key不存在返回空列表;
- ltrim key start end:截取list,保留指定区间内元素,成功返回1,key不存在返回错误;
- lset key index value:设置list中指定下标的元素值,成功返回1,key或者下标不存在返回错误;
- lrem key count value:从key对应list中删除count个和value相同的元素,count为0时候删除全部;
- lpop key:从list的头部删除元素,并返回删除元素,如果key对应list不存在或者是空返回nil,如果key对应值不是list返回错误;
- rpop:同上,但是从尾部删除;
- blpop key1…keyN timeout:从左到右扫描返回对第一个非空list进行lpop操作并返回;
- 比如blpop list1 list2 list3 0,如果list1不存在list2,list3都是非空则对list2做lpop并返回从list2中删除的元素;如果所有的list都是空或不存在,则会阻塞timeout秒,timeout为0表示一直阻塞;
- 当阻塞时,如果有client对key1…keyN中的任意key进行push操作,则第一在这个key上被阻塞的client会立即返回;如果超时发生,则返回nil;有点像unix的select或者poll
- brpop:同blpop,一个是从头部删除一个是从尾部删除;
- rpoplpush srckey destkey:从srckey对应list的尾部移除元素并添加到destkey对应list的头部,最后返回被移除的元素值,整个操作是原子的.如果srckey是空或者不存在返回nil;
- set类型:
- redis的set是string类型的无序集合,set元素最大可以包含(2的32次方-1)个元素;
- set的是通过hash table实现的,所以添加,删除,查找的复杂度都是O(1);hash table会随着添加或者删除自动的调整大小,需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作,可能不久后就会改用跳表(skip list)来实现;
- 跳表已经在sorted set中使用了,关于set集合类型除了基本的添加删除操作,其它有用的操作还包含集合的取并集(union),交集(intersection),差集(difference);通过这些操作可以很容易的实现sns中的好友推荐和blog的tag功能;
- 与set相关的操作:
- sadd key member:添加一个string元素到key对应的set集合中,成功返回1,如果元素以及在集合中返回0,key对应的set不存在返回错误;
- srem key member:从key对应set中移除给定元素,成功返回1,如果member在集合中不存在或者key不存在返回0,如果key对应的不是set类型的值返回错误;
- spop key:删除并返回key对应set中随机的一个元素,如果set是空或者key不存在返回nil;
- srandmember key:同spop,随机取set中的一个元素,但是不删除元素;
- smove srckey dstkey member:从srckey对应set中移除member并添加到dstkey对应set中,整个操作是原子的;成功返回1,如果member在srckey中不存在返回0,如果key不是set类型返回错误;
- scard key:返回set的元素个数,如果set是空或者key不存在返回0;
- sismember key member:判断member是否在set中,存在返回1,0表示不存在或者key不存在;
- sinter key1 key2…keyN:返回所有给定key的交集;
- sinterstore dstkey key1…keyN:同sinter,但是会同时将交集存到dstkey下;
- sunion key1 key2…keyN:返回所有给定key的并集;
- sunionstore dstkey key1…keyN:同sunion,并同时保存并集到dstkey下;
- sdiff key1 key2…keyN:返回所有给定key的差集;
- sdiffstore dstkey key1…keyN:同sdiff,并同时保存差集到dstkey下;
- smembers key:返回key对应set的所有元素,结果是无序的;
- sorted set类型:
- 和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个double类型的score;
- sorted set的实现是skip list和hash table的混合体当元素被添加到集合中时,一个元素到score的映射被添加到hash table中,所以给定一个元素获取score的开销是O(1),另一个score到元素的映射被添加到skip list并按照score排序,所以就可以有序的获取集合中的元素;添加,删除操作开销都是O(log(N))和skip list的开销一致,redis的skip list实现用的是双向链表,这样就可以逆序从尾部取元素;
- sorted set最经常的使用方式应该是作为索引来使用,我们可以把要排序的字段作为score存储,对象的id当元素存储;
- 与sorted set相关的操作:
- zadd key score member:添加元素到集合,元素在集合中存在则更新对应score;
- zrem key member:删除指定元素,1表示成功,如果元素不存在返回0;
- zincrby key incr member:增加对应member的score值,然后移动元素并保持skip list保持有序,返回更新后的score值;
- zrank key member:返回指定元素在集合中的排名(下标),集合中元素是按score从小到大排序的;
- zrevrank key member:同上,但是集合中元素是按score从大到小排序;
- zrange key start end:类似lrange操作从集合中去指定区间的元素;返回的是有序结果;
- zrevrange key start end:同上,返回结果是按score逆序的;
- zrangebyscore key min max:返回集合中score在给定区间的元素;
- zcount key min max:返回集合中score在给定区间的数量;
- zcard key:返回集合中元素个数;
- zscore key element:返回给定元素对应的score;
- zremrangebyrank key min max:删除集合中排名在给定区间的元素;
- zremrangebyscore key min max:删除集合中score在给定区间的元素;
- hash类型:
- redis的hash是一个string类型的field和value的映射表,它的添加,删除操作平均都是O(1),hash特别适合用于存储对象;
- 相较于将对象的每个字段存成单个string类型,将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象,省内存的原因是新建一个hash对象时开始是用zipmap(又称为small hash)来存储的;
- zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销,尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多,所以使用zipmap也是很快的,也就是说添加删除平均还是O(1);
- 如果field或者value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现,这个限制可以在配置文件中指定;
- hash-max-zipmap-entries 64 #配置字段最多64个
- hash-max-zipmap-value 512 #配置value最大为512字节
- 与hash相关的操作:
- hset key field value:设置hash field为指定值,如果key不存在,则先创建;
- hget key field:获取指定的hash field;
- hmget key filed1….fieldN:获取全部指定的hash filed;
- hmset key filed1 value1 … filedN valueN:同时设置hash的多个field;
- hincrby key field integer:将指定的hash filed 加上给定值;
- hexists key field:测试指定field是否存在;
- hdel key field:删除指定的hash field;
- hlen key:返回指定hash的field数量;
- hkeys key:返回hash的所有field;
- hvals key:返回hash的所有value;
- hgetall:返回hash的所有filed和value;
Redis学习01–在Linux下安装Redis v2.6.13
- Redis的介绍:
- redis是一个开源的key-value数据库,它又经常被认为是一个数据结构服务器,因为它的value不仅包括基本的string类型还有list,set,sorted set和hash类型,当然这些类型的元素也都是string类型,也就是说list,set这些集合类型也只能包含string类型;
- 可以在这些类型上做很多原子性的操作,比如对一个字符value追加字符串(APPEND命令),加加或者减减一个数字字符串(INCR命令,当然是按整数处理的),可以对list类型进行push,或者pop元素操作(可以模拟栈和队列),对于set类型可以进行一些集合相关操作(intersection,union,difference);
- memcache也有类似与++,–的命令,不过memcache的value只包括string类型,远没有redis的value类型丰富;
- 和memcahe一样为了性能,redis的数据通常都是放到内存中的,当然redis可以每间隔一定时间将内存中数据写入到磁盘以防止数据丢失;
- redis也支持主从复制机制(master-slave replication),redis的其它特性包括简单的事务支持和发布订阅(pub/sub)通道功能,而且redis配置管理非常简单,还有各种语言版本的开源客户端类库;
- key-list类型内存数据引擎:
- 互联网数据目前基本使用两种方式来存储,关系数据库或者key value,但是这些互联网业务本身并不属于这两种数据类型,比如用户在社会化平台中的关系,它是一个list,如果要用关系数据库存储就需要转换成一种多行记录的形式,这种形式存在很多冗余数据,每一行需要存储一些重复信息;如果用key value存储则修改和删除比较麻烦,需要将全部数据读出再写入; — by timyang@weibo.com
- 如果用key-value中的value存储list,只能实现最简单的列表功能(按照id或时间先后排序,例如使用memcache的append或prepend协议),其他list操作只能靠客户端操作,性能很差,如果数据量较大,操作时间是无法接受的,并发也会遇到巨大挑战);
- key-list系统key对应的”value”是一个list(eg.set list),可以对list中的单个item进行操作,理想的key-list需要如下特点:
- list可以是海量的,且操作性能高效;
- list是可以是有序的,且可动态调整顺序;
- 使用场景:
- 论坛中的主题列表,回复列表;
- 微博中的用户关注列表,用户feed列表,用户关注feed列表;
- 最近访问列表;
- 集合操作:求交集,并集,差集(sdiff/sinter/sunion);
- 好友推荐;
- 排行榜;
- 下载:
- 官方地址: http://redis.io/;
- 下载地址: http://redis.io/download;
- 文档地址: http://redis.io/documentation;
- 作者blog: http://oldblog.antirez.com/;
- 安装:
- 解压缩到/usr/local目录下:tar -zxvf /tools/redis-2.6.13.tar.gz -C /usr/local/;
- 编译:cd /usr/local/redis-2.6.13/;make;
- 安装时报错:undefined reference to `__sync_add_and_fetch_4′;
- 是因为CPU信息判断失误引起的,所以需要确定CPU的信息,然后把CPU的信息设置到环境变量中,清除旧的编译文件,最后重新编译即可:uname -a(uname -m);export CFLAGS=-march=i686;cd /usr/local/redis-2.6.13/;make distclean;make;
- 如果需要运行make-test的话需要安装8.5版本以上的tcl;
- 此时就会在src目录下出现redis-server的服务端和redis-cli客户端,为了方便使用可以分别创建软连接:ln -s /usr/local/redis-2.6.13/src/redis-server /usr/local/redis-2.6.13/src/redisd;ln -s /usr/local/redis-2.6.13/src/redis-cli /usr/local/redis-2.6.13/src/redis;
- 添加PATH变量:PATH=$PATH:/usr/local/redis-2.6.13/src;
- 启动服务端:
- 默认配置启动:redisd;
- 使用自己的配置文件启动:redisd /etc/redis.conf;
- 在后台运行:redisd /etc/redis.conf &;
- 启动客户端:redis;
- 解压缩到/usr/local目录下:tar -zxvf /tools/redis-2.6.13.tar.gz -C /usr/local/;
- 驱动下载:
- 官网提供的支持语言的下载地址: http://redis.io/clients;
- Jedis的github地址:https://github.com/xetorthio/jedis;
- jar包下载地址:https://github.com/xetorthio/jedis/downloads;
- 提供的方法:
- Sorting
- Connection handling
- Commands operating on any kind of values
- Commands operating on string values
- Commands operating on hashes
- Commands operating on lists
- Commands operating on sets
- Commands operating on sorted sets
- Transactions
- Pipelining
- Publish/Subscribe
- Persistence control commands
- Remote server control commands
- Connection pooling
- Sharding (MD5, MurmureHash)
- Key-tags for sharding
- Sharding with pipelining
- 配置文件;
MongoDB学习08–驱动实践
- 目前驱动有两种:
- 官方驱动:
- samus驱动:
- 官方页面:https://github.com/samus;
- c#驱动下载: https://github.com/samus/mongodb-csharp/downloads;
- 有时间使用java写一下CRUD的例子;
MongoDB学习07–运维技术
- 常见的运维技术:
- 安装部署;
- 状态监控;
- 安装认证;
- 备份与恢复;
- 安装部署:
- mongod进程总是停留在命令行窗口下,很容易误操作给结束掉;而且日志信息总是在命令行下打印,不便于之后的查看,可以把mongod作为后台进程运行,并把日志输出到文件中;
- Linux中:mongod –dbpath=/mongo/data –logpath=/mongo/logs/mongod.log –logappend –port=27017 –fork;
- Windows中:mongod –dbpath=D:\mongo\data –logpath=D:\mongo\logs\mongod.log –logappend –port=27017 –install;之后就可以通过[net start/stop MongoDB]命令来开启和关闭mongodb数据库了;
- Linux下开机启动,可以编辑/etc/rc.d/rc.local文件,把mongod启动的脚本添加进去即可;
- Linux下关闭mongod服务器:
- 如果没有使用–fork命令,则直接退出终端即可,mongodb会自动清理;
- 如果使用了–fork命令,在admin数据库下执行:db.shutdownServer();
- 如果在Master-Slave集群中,如果备机和主机时间相差超过10s的话不会关闭mongod,可以通过配置timeoutSecs来完成mongod的数据更新,时间差在10s以内的话就可以关闭了:db.shutdownServer({force : true, timeoutsec : 5});
- 监控状态:
- http监控器,通过页面打开,网页使用端口是mongod服务端口加1000:http://mongod-host:port;
- 打开客户端调用db.serverStatus()获得服务器的状态,包括服务器基本信息,锁,索引,内存,连接,游标,网络和用户操作行为等信息;
- 使用客户端工具mongostat来实时查看统计信息(具体的字段含义可以通过mongostat -h查看),每秒刷新一次:mongostat –port=27017 –all;
- 安全认证:
- mongodb身份验证的机制:
- 当启动mongod服务的时候不加–auth参数,默认是没有权限验证的,可以直接登陆数据库做任何操作而且可以远程访问数据库,相当于都是超级用户的权限;
- 当启动mongod服务的时候添加–auth参数,如果没有在admin数据库中添加过用户(即admin.system.users不存在或者为空),那么还是可以做任何操作,直到在admin.system.users中添加了一个用户(如果没有在admin.system.users中添加用户,而在其它数据库下添加了用户,它们依然是超级用户的权限,因为只有admin.system.users中有用户后mongod的认证授权服务才生效);
- admin.system.users中保存的用户都是超级用户,可以对其它的数据库进行操作,而其它数据库中的用户只能对自己数据库中的集合(不论是哪个用户创建的集合)进行操作;
- mongodb中,数据库是由超级用户来创建的,一个数据库可以包含多个用户,但是一个用户只能在一个数据库中操作,不同的数据库中的用户可以同名;
- 创建用户的语法:db.addUser(username, password[, readOnly=false]);
- username:用户名;
- password:访问的密码;
- readOnly:默认是false,即有读写的权限,如果设置为true的话,则只有读的权限;
- 创建超级用户:mongo –port=27017 admin;db.addUser(“root”, “mongo”);
- 验证用户:
- 可以在登陆时指定用户名密码:mongo 127.0.0.1:27017/admin -uroot -pmongo;
- 登陆之后使用db.auth()函数验证用户:db.auth(“root”, “mongo”),返回1表示成功,返回0表示失败;
- 可以在登陆时指定用户名密码:mongo 127.0.0.1:27017/admin -uroot -pmongo;
- 查看用户:db.getCollection(‘system.users’).find();
- 删除用户:db.removeUser(“root”),也可以使用db.system.users.remove()操作,如果删除所有admin.system.users中的用户,则就没有安全验证了;
- 其它数据库中创建用户的做法相同;
- mongodb身份验证的机制:
- 备份与恢复:
- 冷备份:在关闭服务器的情况下直接拷贝mongodb的数据目录,不推荐;
- mongodump和mongorestore:mongodb提供的内置工具,提供了热备功能,备份的父目录为/mongo/backup;
- mongodump:mongodump -h 127.0.0.1 –port 27017 -d test -o /mongo/backup;
- mongorestore:mongorestore -h 127.0.0.1 –port 27017 -d test -drop /mongo/backup/test;(–drop选项是指定导入数据之前先删除原来的数据)
- 在导出的时候可能数据的实时性不能保证,因为导出的时候可能数据还在内存中,可以使用mongodb提供的fsync+lock机制把缓冲区数据暴力刷入硬盘,然后给数据库一个写入锁,所有的写入都会被阻塞,直到fsync+lock释放锁为止;
- 加锁:db.runCommand({“fsync”:1, “lock”:1});
- 释放锁:db.$cmd.unlock.findOne();
- mongodump:mongodump -h 127.0.0.1 –port 27017 -d test -o /mongo/backup;
- Master-Slave架构;
MongoDB学习06–分片技术
- 当数据量达到T级别的时候,对CPU,内存和磁盘的压力都很大,这个时候就需要采用分片技术,将MongoDB中的集合进行拆分,分担到多个片(即多台MongoDB服务器)上;
- 跨服务器的数据拆分中,Sharding是一个有效的方法;MongoDB中支持自动化Sharding,但是对数据库性能会造成很大影响;因此建议用户尽早进行Sharding,使用MMS:Munin (+ Mongo plugin)和CloudWatch等工具对MongoDB进行监控,确保系统资源使用达到80%之前就完成Sharding工作;
- 分片技术中各个组件的职能及拓扑图:
- clients:客户端,即发出数据请求,并得到返回结果;
- config:保存数据和片的对应关系以及相应的配置信息,配置节点推荐使用使用主备,防止单点故障;
- mongos:即路由服务器,根据设置的片键(即集合拆分的依据)将数据分摊到不同的mongod集群;
- mongod:普通的MongoDB服务器,就是所谓的片;
- 拓扑图;
- 使用不同的目录和端口来模拟不同的服务器:
- config:mongod –dbpath=/mongo/config –port=20001;
- mongos:mongos –port=20002 –configdb=127.0.0.1:20001;
- mongod1:mongod –dbpath=/mongo/mongod1 –port=30001;
- mongod2:mongod –dbpath=/mongo/mongod2 –port=30002;
- mongod3:mongod –dbpath=/mongo/mongod3 –port=30003;
- 开启config服务器:mongod –dbpath=/mongo/config –port=20001;
- 启动mongos服务器(不需要指定数据目录,因为它只是起到路由器的功能),同时要指定config服务器,因为要写入配置信息:mongos –port=20002 –configdb=127.0.0.1:20001;
- 分别启动三台mongod服务器:mongod –dbpath=/mongo/mongodX –port=3000X;
- 配置mongos服务:
- 连接mongos服务并添加分片节点:mongo 127.0.0.1:20002/admin;
- 开启数据库的分片功能,以test数据库为例:db.runCommand({“enablesharding”:”test”});
- 指定集合的分片键,以user集合的name列为例:
- 连接mongos服务并添加分片节点:mongo 127.0.0.1:20002/admin;
- 查看效果:
- 通过mongos插入10w条测试数据;
- 查看数据的分布情况:db.printShardingStatus();
- 结果分析:
- shards:查看到已经分成了shard0000,shard0001,shard0002三个片;
- databases:看到test的partitioned属性为true;
- chundks:被分成了$minKey(无穷小)->user0, user0->user9999, user9999->$maxKey(无穷大)三段;
- 通过mongos插入10w条测试数据;
MongoDB学习05–Master-Slave架构及副本集
- Master-Slave架构的优点及拓扑图:
- 主要的优点:
- 解决单点故障问题;
- 实现数据的备份和恢复;
- 实现数据的读写分离;
- 拓扑图;
- 使用不同的目录和端口来模拟不同的服务器:
- master:mongod –dbpath=/mongo/master –port=27017 –master;
- slave1:mongod –dbpath=/mongo/slave1 –port=30001 –slave –source=127.0.0.1:27017;
- slave2:mongod –dbpath=/mongo/slave2 –port=30002 –slave;
- 主要的优点:
- 启动Master数据库:mongod –dbpath=/mongo/master –port=27017 –master,在master和slave的服务器上都会有一个local的集合,用来存放内部的复制信息;
- 配置Salve1数据库,并在启动时就与Master数据库保持同步:mongod –dbpath=/mongo/slave1 –port=30001 –slave –source=127.0.0.1:27017,会看到有一个rep1的进程从Master服务器同步数据;
- 把原来的Slave2服务器添加到Master-Slave架构中:
- 首先启动一台Slave2服务器,并不指定Master源:mongod –dbpath=/mongo/slave2 –port=30002 –slave;
- 连接到Slave2服务器,在local数据库下添加Master服务器信息,然后查看,同步完成后会出现同步的时间戳;
- 可以通过观察系统日志或者查看集合的记录数来查看同步的状况;
- 首先启动一台Slave2服务器,并不指定Master源:mongod –dbpath=/mongo/slave2 –port=30002 –slave;
- 查看Master, Slave的状态:
- 查看master服务器的状态:db.printReplicationInfo();
- 查看slave服务器的状态:db.printReplicationInfo();
- 查看master服务器的状态:db.printReplicationInfo();
- 读写分离:
- 默认情况下Salve服务器不支持数据的读取,可以通过驱动中的slaveOkay来显示读取Slave数据库,从而减轻Master服务器的压力;
- 也可以让前端程序直接连接到Slave服务器读取数据;
- 副本集:
- 与Master-Slave不同的是,它没有特定的主数据库,如果哪个主数据库宕机了,集群中就会推选出一个从属的数据库作为主数据库,具有自动故障恢复功能,所以一定要保证副本集为奇数个,否则会发生主节点宕机,其它节点为只读的情况;
- 创建一个名为snda的集群;
- 使用不同的服务器和端口来模拟不同的服务器:
- rep1:mongod –dbpath=/mongo/rep1 –port=30001 –replSet=snda/127.0.0.1:30002,127.0.0.1:30003;
- rep2:mongod –dbpath=/mongo/rep2 –port=30002 –replSet=snda/127.0.0.1:30001,127.0.0.1:30003;
- rep3:mongod –dbpath=/mongo/rep3 –port=30003 –replSet=snda/127.0.0.1:30001,127.0.0.1:30002;
- arbriter:mongod –dbpath=/mongo/arbiter –port=30000 –replSet=snda/127.0.0.1:30001;
- 打开第一个副本服务器rep1:mongod –dbpath=/mongo/rep1 –port=30001 –replSet=snda/127.0.0.1:30002,127.0.0.1:30003,replSet参数表示让服务器知道副本集snda下还有其它的服务器;
- 打开第二个副本服务器rep2:mongod –dbpath=/mongo/rep2 –port=30002 –replSet=snda/127.0.0.1:30001,127.0.0.1:30003,会与其它的副本进行通信,并读取副本集的配置;
- 同样的方法打开第三个副本集rep3:mongod –dbpath=/mongo/rep3 –port=30003 –replSet=snda/127.0.0.1:30001,127.0.0.1:30002;
- 添加副本集的配置信息,在任意一个副本上的admin数据库进行配置,这里选择rep1:db.runCommand({replSetInitiate:{_id:”snda”, members:[{_id:1, host:”127.0.0.1:30001″}, {_id:2, host:”127.0.0.1:30002″}, {_id:3, host:”127.0.0.1:30003″}]}});
- 此时可以从日志看出哪一个副本已经成为了Primary服务器,哪一个副本是Secondary服务器,可以看到rep2和rep3都投票给rep1作为primary服务器;当然也可以通过查看状态查询:rs.status();
- 启动仲裁服务器:mongod –dbpath=/mongo/arbiter –port=30000 –replSet=snda/127.0.0.1:30001;
- 添加仲裁服务器配置,可以通过snda副本集中任意一个副本的admin数据库添加,这里以rep1为例:rs.addArb(“127.0.0.1:30000”);
- 查看副本集群中各个服务器的状态:rs.status();
- 测试自动故障恢复功能,关掉当前的主服务器,之后通过rs.status()查看状态,发现自动投票选择了新的主服务器;
- 当rep1重新修复后加入集群,自动成为了Secondary服务器;
MongoDB学习04–性能分析及索引操作
- 性能分析函数explain();
- 语法:cursor.explain(verbose),verbose为true或者1的时候会输出所有的执行计划和旧的执行计划;
- 一般是跟在find()操作后使用(eg:db.mycoll.find().explain()),它的执行速度决定于实际查询的速度,尽管explain()会额外生成一系列候选的执行计划,但是跟实际查询的性能相差不大;
- explain函数的输出结果;
- explain()函数输出结果含义:
- cursor:查找使用的cursor类型和索引名称;
- isMultiKey:是否是联合索引;
- n:返回记录的个数;
- nscannedObjects:遍历的文档对象的个数;
- nscanned:遍历的文档的个数;
- nscannedObjectsAllPlans: <num>;
- nscannedAllPlans: <num>;
- scanAndOrder: <boolean>;
- indexOnly: <boolean>;
- nYields: <num>;
- nChunkSkips: <num>;
- millis:消耗的时间,单位是ms;
- indexBounds:索引的范围,如果为空表示没有用到索引;
- llPlans:产生的所有的候选的执行计划;
- oldPlan:旧的执行计划;
- server:主机名和端口号<host:port>;
- 创建/删除索引,及前后效率的对比;
- 创建索引语法:db.mycoll.ensureIndex(keypattern[,options]);
- keypattern:是索引的键;
- options:可以取name,unique,dropDups,sparse等值;
- 只有在某个列上没有索引才会创建,如果存在索引的话就不再创建;
- 默认创建的是b-tree索引;
- 可以在子文档上创建索引:db.mycoll.ensureIndex({“object.attritude”:1});
- 没有创建索引时,查找name=index10000的效率:db.index.find({“name”:”index10000″}).explain();使用的是BasicCursor即没有索引,扫描了10w条记录后返回1条记录,消耗了192ms;
- 在name列创建一个名为idx_index_name的索引:db.index.ensureIndex({name:1}, {name:”idx_index_name”}),如果不指定第二个参数中的name的话,则系统会自动生成一个索引的名称;
- 查看创建索引后的效率,发现使用了Btree索引,一共扫描一条记录,返回1条记录,并且瞬间返回;
- 删除索引语法:db.mycoll.dropIndex(index),可以根据名称删除索引,或者根据创建时指定的键值;
- db.mycoll.dropIndex(“indexName”):根据索引名称删除;
- db.mycoll.dropIndex({ “indexKey” : 1 }):根据key值删除索引;
- 如果要删除某个集合下所有的索引,则使用db.mycoll.dropIndexes(),但是无法删除系统自动创建的基于_id列的索引,它由数据库自己维护;
- 重建索引:db.mycoll.reIndex(),是把原来的索引删掉后重建;
- 创建索引语法:db.mycoll.ensureIndex(keypattern[,options]);
- 唯一索引:
- 语法:db.mycoll.ensureIndex(keypattern, {unique:true});
- 创建一个基于name列的唯一索引unq_index_name:db.index.ensureIndex({name:1}, {name:”unq_index_name”, unique:true});
- 如果某一个列上已经存在的重复值,在这样的列上创建唯一索引的话可以使用dropDups选项,这样系统会保留第一条记录,删除剩下重复的记录:db.mycoll.ensureIndex(keypattern, {unique:true, dropDups:true});
- 如果某一个列是唯一列,插入数据时没有插入此列的话会保存一个null值,一个唯一列中只能有一个null值;(这点与关系型数据库不同,关系型数据库中唯一列中可以保存null值,但是在mongodb中null值作为了一个值来比较)
- 组合索引:
- 语法与普通索引相同,只是需要多指定几个列:db.mycoll.ensureIndex({key1:value1, key2:value2, …});
- 键后面的数字表示索引的存储顺序,其中1表示升序,-1表示降序.在随机访问的时候意义不大,但是在排序或者是范围查询时很重要;
- 如果在a,b,c三列上创建了这索引,则以a,ab,abc开头的条件都可以使用到此索引;
- 稀疏索引:
- Sparse indexes only contain entries for documents that have the indexes field, any document that is missing the field is not indexes;
- By contrast, non-sparse indexes contrain all documents in collection, and store null values for documents that do not contrain the indexes field;
- 语法:db.mycoll.ensureIndex(keypattern, {sparse:true});
- 稀疏索引与非稀疏索引的对比;
- hint:
- 查询优化器会选择最优的执行计划,而且第一次执行后的执行计划将会被保存;如果需要使用自己指定的查询方案的话可以使用hint;
- 语法:cursor.hint(index);
- db.mycoll.find().hint(“index_name”):通过指定索引名称来实现hint;
- db.mycoll.find().hint({key:value}):通过指定创建索引时的key的顺序来实现hint,可以之前通过db.mycoll.getIndexes()查看;
- 查看hint的性能:db.mycoll.find().hint(index).explain();
- 注意事项:
- MongoDB中的索引是大小写敏感的;
- 当更新对象时,只有在索引上的这些key发生变化才更新,所以提高了性能;当对象增长了或者移动时,所有的索引都必须更新,会很慢;
- 索引的信息会保存在system.indexes集合中,运行db.system.indexes.find()可以查看这些数据;
- 索引的字段的大小限制目前是800bytes,大于之后可以在这个字段上创建索引,但是该字段不会被索引;
- 如果数据集合比较小(4M),可以联合使用limit()和sort()函数而不需要创建索引来查询数据;
.png)
.png)
.png)
.png)
.png)
.png)
MongoDB学习03–聚合操作,游标的使用及排序分页操作
- 聚合操作:
- count:查看符合某些条件的集合中记录的个数;
- 查看集合user的记录数:db.user.count();
- 查看集合user中年龄大于20的记录的个数:db.user.count({“age”:{$gt:20}});
- 查看集合user的记录数:db.user.count();
- distinct:查看集合中某个属性的独立存在的个数;
- 获得不同年龄的数组:db.user.distinct(“age”);
- 获得不同年龄值的个数:db.user.distinct(“age”).length;
- 获得不同年龄的数组:db.user.distinct(“age”);
- group:对集合按照某一属性分组,形成一个K-V模型;
- 语法:db.mycoll.group( { key : …, initial: …, reduce : … [, finalize: …] [, condition: …] } );
- key:分组的键;
- initial:每个分组使用的初始化函数;
- reduce:此函数的第一个参数是当前的文档对象,第二个参数是上次函数(即initial函数)操作的累计对象,有多少个文档,$reduce就会调用多少次;
- finalize:每一组文档执行完之后,会触发执行的一个函数,参数是上次函数(即initial函数)操作的累计对象;
- condtion:过滤条件;
- 按照年龄分组的例子:db.user.group({key:{age:true}, initial:{user:[]}, reduce:function(cur, prev){prev.user.push(cur.name);}}),push函数是把一个值加入到数组中;
- 按照年龄分组,过滤掉年龄大于25的记录,并且添加一个用户数量的属性count:db.user.group({key:{age:true}, initial:{user:[]}, reduce:function(cur, prev){prev.user.push(cur.name);}, finalize:function(prev){prev.count=prev.user.length;}, condition:{age:{$lte:25}}});
- 语法:db.mycoll.group( { key : …, initial: …, reduce : … [, finalize: …] [, condition: …] } );
- mapReduce:其实是一种编程模型,用在分布式计算中.可以把问题划分为多个不同的部分并分发到不同的服务器并行处理,每台服务器都把分配给自己的一部分处理完成后把结果集返回给主服务器,主服务器汇总结果后完成问题的处理;
- 语法:db.mycoll.mapReduce( mapFunction , reduceFunction , <optional params> );
- mapFunction:映射函数,里面会调用emit(key, value),集合会按照指定的key进行映射分组;
- reduceFunction:;简化函数,会对map分组后的数据进行分组简化.其中reduce(key, value)中的key就是emit中的key,value为emit分组后的emit(value)的集合,它有一个或者多个对应于键的文档组成;
- 可选参数;
- 原理:map首先将文档映射到集合并操作文档,这一步可能产生多个键和多个值或者什么也没有(文档中要处理的值为空).而后按照键分组,并将产生的值组成列表放到对应的键中,reduce则把列表中的值化简为一个值,这个值被返回,而后继续按键分组,进行化简,直到每个键在列表中只有一个值为止,这个值也就是最终结果;
- 例子;
- 语法:db.mycoll.mapReduce( mapFunction , reduceFunction , <optional params> );
- count:查看符合某些条件的集合中记录的个数;
- 游标的使用:
- 使用while循环遍历输出:var cursor=db.user.find(); while(cursor.hasNext()) { printjson(cursor.next()); };(其中hasNext()函数判断是否有记录,next()函数返回下一条记录)
- pringjson()函数是内置函数,能够将结果输出为json格式;
- 得到数据集集合,遍历完之后游标便销毁;
- 使用forEach()函数遍历:db.user.find().forEach(printjson);
- 像访问数组一样使用游标:var cursor=db.user.find();printjson(cursor[0]);(会把访问的数据都加载ram中,如果数据量很大的话非常消耗内存)
- 直接转换成数组访问:var array=db.user.find().toArray();array[0];
- 使用while循环遍历输出:var cursor=db.user.find(); while(cursor.hasNext()) { printjson(cursor.next()); };(其中hasNext()函数判断是否有记录,next()函数返回下一条记录)
- 排序操作:
- 语法:db.mycoll.find().sort({col:value, …});
- col:表示按照排序的列;
- value:可以取1和-1,1表示升序,-1表示降序;
- 查找所有记录,并按照年龄升序,名称降序显示:db.user.find().sort({“age”:1, “name”:-1}),select name, age from user order by age desc, name asc;
- 还可以通过其它方法实现,db.mycoll.find([query], [fields])操作是根据query过滤记录,先后显示fields里面指定的列,如果query给默认的话({}就是默认的参数),只需要指定列名及排序方式即可:select name, age from user order by age desc, name asc;
- 先按年龄升序,然后按照名称升序排序:select name from user order by age asc, name asc;
- 语法:db.mycoll.find().sort({col:value, …});
- 分页操作:
- 使用db.mycoll.find().skip(n)操作和db.mycoll.find().limit(n)实现,前者是跳过之前多少条记录,后者是显示多少条记录:skip((pageIndex-1) * pageSize).limit(pageSize);
- 排序后的结果,每页两条记录,显示第二页:db.user.find().sort({“age”:1, “name”:-1}).skip(2).limit(2);
.png)
.png)
.png)
.png)
.png)
MongoDB学习02–增删改查操作
- INSERT操作:
- 单条插入(可以使用js的语法);
- 批量插入:mongodb中并没有提供批量插入的语法,但是高级语言中提供了与mongodb批量插入的接口,也可以通过for循环来模拟;
- insert和save的区别:
- 如果不使用_id列,两个函数没有区别都是插入数据;
- 如果使用了_id列,insert插入_id重复的话会报错;save插入_id重复的话会调用upsert,即存在就更新,不存在就插入;
- 单条插入(可以使用js的语法);
- FIND操作:
- 根据条件查询:[>, >=, <, <=, !=, =]分别对应[$gt, $gte, $lt, $lte, $ne, 无]关键字;
- 多条件查询:[and, or, in, notin]分别对应[无, $or, $in, $nin]关键字;
- 正则表达式查询;
- 使用$where子句查询;
- findOne()函数返回满足条件的第一条记录或者是null:printjson(db.user.findOne({name:”joe”}));
- 根据条件查询:[>, >=, <, <=, !=, =]分别对应[$gt, $gte, $lt, $lte, $ne, 无]关键字;
- UPDATE操作:
- 整体更新;
- 局部更新,使用修改器$inc和$set来更新某一个字段;
- $inc:increase的缩写,即在原来值的基础上增加$inc指定的值,可以用于自增的主键;
- $set:即设置某个列的值为$set指定的值;
- $inc:increase的缩写,即在原来值的基础上增加$inc指定的值,可以用于自增的主键;
- upsert操作:update操作的第一个参数是查询条件,第二个参数是要更新的值,如果设置第三个参数为true的话,就表示如果存在记录就更新,如果不存在就插入;
- multi操作:批量更新,如果update操作匹配到了多条记录,默认情况下只更新第一条,如果需要全部更新的话,需要把update操作中第四个参数设置为true;
- 整体更新;
- REMOVE操作:
- 根据条件删除:db.mycoll.remove(query);
- 全部删除:db.mycoll.remove();
- 根据条件删除:db.mycoll.remove(query);
db.user.find()
db.user.count()
MongoDB学习01–Linux下安装MongoDB v2.2
- 官网地址:http://www.mongodb.org;
- 下载文件:从http://www.mongodb.org/downloads页面下载相应的版本,这里选择linux下32bit的2.2.4版本;
- 解压文件:tar -zxvf /tools/mongodb-linux-i686-2.2.4.tgz;
- 修改文件名,并移动到/usr/local目录下:mv mongodb-linux-i686-2.2.4 mongodb; cp -R /tools/mongodb /usr/local/;
- 把mangodb的路径添加到path中:PATH=$PATH:/usr/local/mongodb/bin;
- 启动mongod服务并修改默认数据目录:
- mongodb默认的数据文件的路径是/data/db,需要预先创建,如果使用的是mongo用户的话,需要修改此目录的权限:chown mongo /data/db;
- 修改数据文件的路径:mkdir -p /mongo/data/journal;mongod –journal –dbpath /mongo/data/;(默认是不开启journal功能的,可以指定参数打开)
- 访问相应的端口查看信息:http://192.168.10.112:28017/;
- 使用配置文件方式启动mongod服务,每次启动都需要输入很长的参数比较麻烦,可以把参数写在/etc/mongod.conf文件中,然后采用:mongod -f /etc/mongod.conf的方式启动;
- 登陆客户端,使用mongo命令,同时也是js的编辑器(可以使用一切js的语法),默认连接test数据库;(使用SecureCRT的话,命令总是会重复一次,推荐使用xshell)
- 获得帮助,使用help命令;
- 关闭mongod服务:mongod –dbpath /mongo/data –shutdown;
- 如果非法关闭后再次打开时报错:mongod –journal –dbpath=/mongo/data,此时需要删除rm -rf /mongo/data/mongod.lock文件,然后再打开;
- 对MongoDB的基本CRUD操作:
- 插入一个集合,是以bson(json的扩展)的形式插入的;
- find操作,_id字段是数据库默认加的guid,目的是保证数据的唯一性;
- update操作,第一个参数为查找的条件,第二个参数为更新的值;
- remove操作,如果不加参数就会删除所有的数据,而且不能回滚;
- 插入一个集合,是以bson(json的扩展)的形式插入的;
- 官方给定的使用mongodb的一些建议:
- MongoDB分成32位版本和64位版本,由于MongoDB使用内存映射文件,所以32位版本只能存储2GB左右的数据;建议存储更多数据的用户使用64位版本;
- MongoDB是文档型数据库,数据以BSON形式存储在文档中;最新版本的MongoDB能够支持最大16MB的文档大小;建议用户尽量不要存储大型对象,将文档控制在1 MB以内;
- MongoDB的写入和更新速度非常快,所以错误提示并不明确;要确保写入正确,建议用户使用getLastError或者使用安全写入;
- 关系型数据库往往会有预定义的schema,你想添加额外的列就需要在整个表上添加;MongoDB没有这个约束,这使得开发和管理变得更简单;但这并不意味着你就可以完全忽视MongoDB的schema设计,一个设计良好的schema能够让MongoDB的性能达到最佳;
- MongoDB的更新在默认情况下会使用类似于传统数据库的LIMIT语句,即LIMIT 1.因此更新不会影响到所有的文档,如果你想要一次更新许多文档,那么请把multi设为true;
- MongoDB默认情况下是区分大小写的,例如db.people.find({name: ‘Russell’}) 和db.people.find({name: ‘russell’})就是不一样的;所以用户需要知道MongoDB的大小写限制;
- 传统数据库中,如果插入错误的数据类型,通常会提示错误或者强制转换成预定义的数据值;MongoDB中没有这种限制,所以输入错误数据类型不会出现提示;建议用户确保输入正确的数据类型;
- 全局锁是一直被MongoDB用户诟病的特性,MongoDB 2.2中增加了数据库级锁,这是一个很大的改进;建议用户使用稳定版的MongoDB 2.2数据库,避免全局锁限制;
- 过期版本MongoDB用户在下载程序包时会出问题,建议用户使用10gen最新版本的官方程序包;
- Replica Set是MongoDB中受关注最多的功能,它能为MongoDB集群增加冗余并提供良好的读性能;但由于Replica Set的选举机制,必须保证Replica Set成员数目为奇数;如果是偶数的话,主节点宕机就会导致其他节点变为只读;解决方法也可以使用一个仲裁节点(arbiter),它也是一个Replica Set的成员,但并不存储用户数据;所以请记住设置Replica Set成员时要定为奇数;
- MongoDB中不存在join,你要针对多个集合进行数据检索的时候,必须使用多个查询;所以当你遇到这个问题时,可以考虑重新设计MongoDB的schema;
- Journaling日志是MongoDB中非常好的功能,能够增强节点的可用性;在2.0版本之后,MongoDB默认是开启Journaling日志功能的;虽然Journaling日志会对数据库性能造成一定的影响,但这部分影响是可以忽略的;因此建议用户开启Journaling功能,特别是对于可用性要求较高的用户;
- MongoDB默认情况下是没有认证功能的,因此建议用户使用防火墙对MongoDB进行保护;
- Replica Set的工作是通过传送oplog来完成的,主节点发生故障后,新的数据将会存放在数据目录下的一个特定文件夹内,即rollback文件夹;你可以用来手动完成数据恢复;所以在每次故障发生之后,你一定要看看这个文件夹,MongoDB自带的工具就能够帮助你轻松地完成手动数据恢复;
- 跨服务器的数据拆分中,Sharding是一个有效的方法;MongoDB中支持自动化Sharding,但是对数据库性能会造成很大影响;因此建议用户尽早进行Sharding,使用MMS:Munin (+ Mongo plugin)和CloudWatch等工具对MongoDB进行监控,确保系统资源使用达到80%之前就完成Sharding工作;
- MongoDB使用shard key来决定特定的文档在哪个分片上,当插入一个文档之后,你是无法更新shard key的;这里建议用户删除文档并重新插入,这样就能够将其分配到合适的分片上;
- MongoDB对分片的限制还包括集合的大小,当超过256 GB的时候,MongoDB将不允许进行分片;相信10gen公司会在未来放弃这一限制,但在此之前用户需要留意;
- MongoDB中跨分片并没有强制要求唯一性,MongoDB只针对独立的分片进行强制而非全局性;当然除shard key之外;
- 进行拆分的时候,MongoDB会要求你选择一个键;用户需要注意选择正确的键,否则会造成不必要的麻烦;如何进行选择并无定式,主要取决于你的应用,比如针对news feed使用时间戳就是错的;在下一版本中,MongoDB将对此进行改进;
- MongoDB连接默认情况下是不加密的,也就是说你的数据是能够被第三方记录和使用的;所以你在公共网中访问MongoDB的话,就一定要进行加密;
- MongoDB只支持单一文档的原子性,这一点与传统的数据库有所不同,如MySQL.因此MongoDB中跨多个文档是不提供内置的transaction支持的;
- 当MongoDB显示ready的时候,其实内部还在进行journal的配置;因此针对速度较慢的文件系统,MongoDB的journal配置也会很慢;
- 不建议尝试NUMA + Linux + MongoDB的组合,如果你的MongoDB跑在NUMA服务器上,建议将它关掉;
- 在Linux上运行MongoDB遭遇segfault错误时,这主要是因为open files / process限制过低;建议用户将限制设定为4K+;
分布式文档存储数据库–MongoDB
MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。他支持的数据结构非常松散,是类似json的bjson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是他支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
它的特点是高性能、易部署、易使用,存储数据非常方便。主要功能特性有:
- 面向集合存储,易存储对象类型的数据。
- 模式自由。
- 支持动态查询。
- 支持完全索引,包含内部对象。
- 支持查询。
- 支持复制和故障恢复。
- 使用高效的二进制数据存储,包括大型对象(如视频等)。
- 自动处理碎片,以支持云计算层次的扩展性
- 支持RUBY,PYTHON,JAVA,C++,PHP等多种语言。
- 文件存储格式为BSON(一种JSON的扩展)
- 可通过网络访问
所谓“面向集合”(Collenction-Orented),意思是数据被分组存储在数据集中,被称为一个集合(Collenction)。每个 集合在数据库中都有一个唯一的标识名,并且可以包含无限数目的文档。集合的概念类似关系型数据库(RDBMS)里的表(table),不同的是它不需要定 义任何模式(schema)。
模式自由(schema-free),意味着对于存储在mongodb数据库中的文件,我们不需要知道它的任何结构定义。如果需要的话,你完全可以把不同结构的文件存储在同一个数据库里。
存储在集合中的文档,被存储为键-值对的形式。键用于唯一标识一个文档,为字符串类型,而值则可以是各中复杂的文件类型。我们称这种存储形式为BSON(Binary Serialized dOcument Format)。
MongoDB服务端可运行在Linux、Windows或OS X平台,支持32位和64位应用,默认端口为27017。推荐运行在64位平台,因为MongoDB在32位模式运行时支持的最大文件尺寸为2GB。
MongoDB把数据存储在文件中(默认路径为:/data/db),为提高效率使用内存映射文件进行管理。
NoSQL的现状
经过了至少4年的激烈争论,现在是对NoSQL的现状做一个阶段性结论的时候了。围绕着NoSQL发生了如此之多的事情,以至于很难对其作出一个简单概括,也很难判断它达到了什么目标以及在什么方面没有达到预期。
在很多领域,NoSQL不仅在行业内也在学术领域中取得了成功。大学开始认识到NoSQL必须要加入到课程中。只是反复讲解标准数据库已经不够了。当然,这不意味着深入学习关系型数据库是错误的。相反,NoSQL是很好的很重要的补充。
发生了什么?
NoSQL领域在短短的4到5年的时间里,爆炸性地产生了50到150个新的数据库。nosql-database.org列出了150个这样的数据库,包括一些像对象数据库这样很古老但很强大的。当然,一些有意思的合并正在发生,如CouchDB和Membase交易产生的CouchBase。但是我们稍后会在本文中讨论每一个主要的系统。
很多人都曾经假设在NoSQL领域会有一个巨大地整合。但是这并没有发生。NoSQL过去是爆炸性地增长,现在依旧如此。就像计算机科学中的所有领域一样——如编程语言——现在有越来越多的空白领域需要大量的数据库。这是与互联网、大数据、传感器以及将来很多技术的爆炸性增长同步的,这导致了更多的数据以及对它们进行处理的不同需求。在过去的四年中,我们只看到了一个重要的系统离开了舞台:德国的Graph数据库Sones。为数众多的NoSQL依然快乐地生存着,要么在开源社区,不用考虑任何的金钱回报,要么在商业领域。
可见性与金钱?
另外一个重要的方面就是可见性与行业采用的情况。在这个方面,我们可以看到在传统的行业中——要保护投资——与新兴的行业(主要是初创公司)之间有很大的差别。几乎所有热门的基于Web的创业公司如Pinterest和Instagram 都在使用混合式(SQL + NoSQL)的架构,而传统的行业依然纠结于是否采用NoSQL。但是观察显示,越来越多这样的公司正在试图将它们的一部分数据流用NoSQL方案进行处理并在以后进行分析,这样的方案包括Hadoop、MongoDB以及Cassandra等。
这同时导致了对具备NoSQL知识的架构师和开发人员的需求持续增长。最近的调查显示行业中最需要的开发人员技能如下:
- HTML5
- MongoDB
- iOS
- Android
- Mobile Apps
- Puppet
- Hadoop
- jQuery
- PaaS
- Social Media
在前十名的技术需求中,有两个NoSQL数据库。有一个甚至排在了iOS前面。如果这不是对它的赞扬,那是什么呢?!
但是,跟最初预计相比,对NoSQL的采用变得越来越快,越来越深入。在2011年夏天,Oracle曾经发布过一个著名白皮书,它提到NoSQL数据库感觉就像是冰淇淋的风味,但是你不应该过于依附它,因为它不会持续太长时间。但是仅仅在几个月之后,Oracle就展现了它们将Hadoop集成到大数据设备的方案。甚至,他们建立了自己的NoSQL数据库,那是对BerkeleyDB的修改。从此之后,所有的厂商在集成Hadoop方面展开了竞赛。Microsoft、Sybase、IBM、Greenplum、Pervasive以及很多的公司都已经对它有了紧密的集成。有一个模式随处可见:不能击败它,就拥抱它。
但是,关于NoSQL被广泛采用的另一个很重要但不被大家关注的重要信号就是NoSQL成为了一个PaaS标准。借助于众多NoSQL数据库的易安装和管理,像Redis和MongoDB这样的数据库可以在很多的PaaS服务中看到,如Cloud Foundry、OPENSHIFT、dotCloud、Jelastic等。随着所有的事情都在往云上迁移,NoSQL会对传统的关系型数据库产生很大的压力。例如当面临选择MySQL/PostGres或MongoDB/Redis时,将会强制人们再三考虑他们的模型、需求以及随之而来的其他重要问题。
另外一个很有意思的技术指示器就是ThoughtWorks的技术雷达,即便你可能不完全同意它所包含的所有事情,但它总会包含一些有意思的事情。让我们看一下他们2012年10月份的技术雷达,如图1:
图1:ThoughtWorks技术雷达,2012年10月——平台
在他们的平台象限中,列出了5个数据库:
- Neo4j (采用)
- MongoDB(试用阶段但是采用)
- Riak(试用)
- CouchBase(试用)
- Datomic(评估)
你会发现它们中至少有四个获得了很多的风险投资。如果你将NoSQL领域的所有风险投资加起来,结果肯定是在一亿和十亿美元之间!Neo4j就是一个例子,它在一系列的B类资助中得到了一千一百万美元。其他得到一千万到三千万之间资助的公司是Aerospike、Cloudera、DataStax、MongoDB以及CouchBase等。但是,让我们再看一下这个列表:Neo4j、MongoDB、Riak以及CouchBase已经在这个领域超过四年了并且在不断地证明它们是特定需求的市场领导者。第五名的数据库——Datomic——是一个令人惊讶的全新数据库,它是由一个小团队按照全新的范式编写的。这一定是很热门的东西,在后面简要讨论所有数据库的时候,我们更更深入地了解它们。
标准
已经有很多人要求NoSQL标准了,但他们没有看到NoSQL涵盖了一个范围如此之大的模型和需求。所以,适用于所有主要领域的统一语言如Wide Column、Key/Value、Document和Graph数据库肯定不会持续很长时间,因为它不可能涵盖所有的领域。有一些方式,如Spring Data,试图建立一个统一层,但这取决于读者来测试这一层在构建多持久化环境时是不是一个飞跃。
大多数的Graph和Document数据库在它们的领域中已经提出了标准。在Graph数据库世界,因为它的tinkerpop blueprints、Gremlin、Sparql以及Cypher使得它更为成功一些。在Document数据库领域,UnQL和jaql填补了一些位置,尽管前者缺少现实世界NoSQL数据库的支持。但是借助Hadoop的力量,很多项目正在将著名的ETL语言如Pig和Hive使用到其他NoSQL数据库中。所以标准世界是高度分裂的,但这只是因为NoSQL是一个范围很广的领域。
格局
作为最好的数据库格局图之一,是由451 Group的Matt Aslett在一个报告中给出的。最近,他更新了该图片从而能够让我们可以更好得深入理解他所提到的分类。你可以在下面的图片中看到,这个格局是高度碎片化和重叠的:
(点击图片放大)
图2:Matt Aslett(451 Group)给出的数据库格局
你可以看到在这个图片中有多个维度。关系型的以及非关系型的、分析型的以及操作型的、NoSQL类型的以及NewSQL类型的。最后的两个分类中,对于NoSQL有著名的子分类Key-Value、Document、Graph以及Big Tables,而对于NewSQL有子分类Storage-Engine、Clustering-Sharding、New Database、Cloud Service Solution。这个图有趣的地方在于,将一个数据放在一个精确的位置变得越来越难。每一个都在拼命地集成其他范围数据库中的特性。NewSQL系统实现NoSQL的核心特性,而NoSQL越来越多地试图实现“传统”数据库的特性如支持SQL或ACID,至少是可配置的持久化机制。
这一切都始于众多的数据库都提供与Hadoop进行集成。但是,也有很多其他的例子,如MarkLogic开始参与JSON浪潮,所以也很难对其进行定位。另外,更多的多模型数据库开始出现,如ArangoDB、OrientDB和AlechemyDB(现在它是很有前途的Aerospike DB的一部分)。它们允许在起始的时候只有一个数据库模型(如document/JSON模型)并在新需求出现的时候添加新的模型(Graph或key-value)。
图书
另外一个证明它开始变得成熟的标志就是图书市场。在2010年和2011年两本德语书出版之后,我们看到Wiley出版了Shashank Tiwari的书。它的结构很棒并且饱含了深刻伟大的见解。在2012年,这个竞赛围绕着两本书展开。“七周七数据库”(Seven Databases in Seven Weeks)当然是一本杰作。它的特点在于新颖的编写以及实用的基于亲身体验的见解:它选取了6种著名的NoSQL数据库以及PostGreSQL。这些都使得它成为一本高度推荐的图书。另一方面,P.J. Sandalage以及Martin Fowler采取了一种更为全面的方法,涵盖了所有的特征并帮助你评估采用NoSQL的路径和决策。
但是,会有更多的书出现。Manning的书出现在市场上只是个时间问题:Dan McCreary和Ann Kelly正在编写一本名为“Making Sense of NoSQL”的书,首期的MEAP(指的是Manning Early Access Program——译者注)章节已经可以看到了。
在介绍完理念和模式后,他们的第三章看起来保证很有吸引力:
- 构建NoSQL大数据解决方案
- 构建NoSQL搜索解决方案
- 构建NoSQL高可用性解决方案
- 使用NoSQL来提高敏捷性
只是一个全新的方式,绝对值得一读。
领导者的现状
让我们快速了解一下各个NoSQL的领导者。作为市场上很明显的领导者之一,Hadoop是一个很奇怪的动物(作者使用这个词,可能是因为Hadoop的标识是一只大象——译者注)。一方面,它拥有巨大的发展势头。正如前面所说,每个传统的数据库提供商都急切地声明支持Hadoop。像Cloudera和MapR这样的公司会持续增长并且新的Hadoop扩展和继承者每周都在出现。
即便是Hive和Pig也在更好地得到接受。不过,有一个美中不足之处:公司们依然在抱怨非结构化的混乱(读取和解析文件本应该更快一些),MapReduce在批处理上做的还不够(甚至Google已经舍弃了它),管理依旧很困难,稳定性问题以及在本地很难找到培训/咨询。即便你可以解决一些上面的问题,如果Hadoop继续像现在这样发展或发生重大变化的话,它依然会是热点问题。
第二位领导者,MongoDB,同样面临激烈的争论。处于领导地位的数据库会获得更多的批评,这可能是很自然的事情。不过,MongoDB经历了快速的增长,它受到的批评主要如下:
a)就老版本而言或者
b)缺少怎样正确使用它的知识。尽管MongoDB在下载区域清楚地表明32位版本不能处理2GB的数据并建议使用64位版本,但这依然受到了很多近乎荒谬的抱怨。
不管怎样,MongoDB合作者和资助者推动了雄心勃勃的发展路线,包含了很多热门的东西:
- 行业需要的一些安全性/LDAP特性,目前正在开发
- 全文本搜索很快会推出
- 针对MapReduce的V8将会推出
- 将会出现比集合级别更好的锁级别
- Hash分片键正在开发中
尤其是最后一点吸引了很多架构师的兴趣。MongoDB经常被抱怨(同时也被竞争对手)没有实现简洁一致的哈希,因为key很容易定义所以不能保证完全正确。但在将来,将会有一个对hash分片键的配置。这意味着用户可以决定使用hash key来分片,还是需要使用自己选择分片key所带来的优势(可能很少)。
Cassandra是这个领域中的另一个产品,它做的很好并且添加了更多更好的特性,如更好的查询。但是不断有传言说运行Cassandra集群并不容易,需要一些很艰难的工作。但这里最吸引人的肯定是DataStax。Cassandra的新公司——获得了两千五百万美元的C类资助——很可能要处理分析和一些操作方面的问题。尤其是分析能力使得很多人感到惊讶,因为早期的Cassandra并没有被视为强大的查询机器。但是这种现状在最近的几个版本中发生了变化,查询功能对一些现代分析来讲已经足够了。
Redis的开发进度也值得关注。尽管Salvatore声明如果没有社区和Pieter Noordhuis的帮助,他做不成任何的事情,但是它依旧是相当棒的一个产品。对故障恢复的良好支持以及使用Lua的服务器端脚本语言是其最近的成就。使用Lua的决策对社区带来了一些震动,因为每个人都在集成JavaScript作为服务器端的语言。但是,Lua是一个整洁的语言并为Redis开启新的潘多拉盒子带来了可能性。
CouchBase在可扩展性和其他潜在因素方面看起来也是一个很好的选择,尽管Facebook以及Zynga面临着巨大的风波。它确实不是很热门的查询机器,但如果他们能够在将来提高查询能力,那它的功能就会相当完整了。与CouchDB创立者的合并毫无疑问是很重要的一个步骤,CouchDB在CouchBase里面的影响值得关注。在每个关于数据库的会议上,听到这样的讨论也是很有意思的,那就是在Damien、Chris和Jan离开后,CouchDB会变得更好呢还是更坏呢?大家在这里只能听到极端的观点。但是,只要数据库做得好谁关心这个呢。现在看起来,它确实做的很好。
最后一个需要提及的NoSQL数据库当然是Riak,在功能性和监控方面它也有了巨大的提升。在稳定性方面,它继续得到巨大的声誉:“像巨石一般稳定可靠且不显眼,并对你的睡眠有好处”。Riak CS fork在这种技术的模块化方面看起来也很有趣。
有意思的新加入者
除了市场领导者,评估新的加入者通常是很有意思的。让我们深入了解它们中的一部分。
毫无疑问,Elastic Search是最热门的新NoSQL产品,在一系列的A轮资助中它刚刚获得了一千万美元,这是它热门的一个明证。作为构建在Lucene之上的高扩展性搜索引擎,它有很多的优势:a)它有一个公司提供服务并且b)利用了Lucene在过去的多年中已被充分证明的成就。它肯定会比以往更加深入得渗透到整个行业中,并在半结构化信息领域给重要的参与者带来冲击。
Google在这个领域也推出了小巧但是迅速的LevelDB。在很多特殊的需求下,如压缩集成方面,它作为基础得到了很多的应用。即使是Riak都集成了LevelDB。考虑到Google的新数据库如Dremel和Spanner都有了对应的开源项目(如Apache Drill或Cloudera Impala),它依然被视为会继续存在的。
另外一个技术变化当然就是在2012年初的DynamoDB。自从部署在Amazon中,他们将其视为增长最快的服务。它的可扩展性很强。新特性开发地比较慢但它关注于SSD,其潜力是很令人振奋的。
多模块数据库也是值得关注的一个领域。最著名的代表者是OrientDB,它现在并不是新的加入者但它在很迅速地提高功能。可能它变化得太快了,很多使用者也许会很开心地看到OrientDB已经到达了1.0版本,希望它能更稳定一些。对Graph、Document、Key-Value的支持以及对事务和SQL的支持,使得我们有理由给它第二次表现的机会。尤其是对SQL的良好支持使得它对诸如Penthao这样的分析解决方案方面很有吸引力。这个领域另一个新的加入者是ArangoDB,它的进展很快,并不畏惧将自己与已确定地位的参与者进行比较。
但是,如果有新的需求必须要实现并且具有不同类型的新数据模型要进行持久化的话,对原生JSON和Graph的支持会省去很多的努力。
到目前位置,2012年的最大惊喜来自于Datomic。它由一些摇滚明星采用Clojure语言以难以令人置信的速度开发的,它发布了一些新的范式。另外,它还进入了ThoughtWorks的技术雷达,占据了推荐关注的位置。尽管它“只是”已有数据库中一个参与者,但是它有很多的优势,如:
- 事务
- 时间机器
- 新颖且强大的查询方式
- 新的模式方式
- 缓存以及可扩展性的特性
目前,支持将DynamoDB、Riak、CouchBase、Infinispan以及SQL作为底层的存储引擎。它甚至允许你同时混合和查询不同的数据库。很多有经验的人都很惊讶于这种颠覆性的范式转变是如何可能实现的。但幸运的是它就是这样。
总结
作为总结,我们做出三点结论:
- 关于CAP理论,Eric Brewer的一些新文章应该几年前就发表。在这篇文章中(这篇佳文的中文版地址——译者注),他指出“三选二”具有误导性,并指出了它的原因,世界为何远比简单的CP/AP更为复杂,如在ACID/BASE之间做出选择。虽然如此,近些年来有成千上万的对话和文章继续赞扬CAP理论而没有任何批评性的反思。Michael Stonebraker是NoSQL最强有力的审查者之一(NoSQL领域也对他颇多感激),他在多年前就指出了这些问题!遗憾的是,没有多少人在听。但是,既然Eric Brewer更新了他的理论,简单的CAP叙述时代肯定要结束了。在指出CAP理论的真实和多样性的观点上,请站在时代的前列。
- 正如我们所了解的那样,传统关系型数据库的不足导致了NoSQL领域的产生。但这也是传统帝国发起回击的时刻。在“NewSQL”这个术语之下,我们可以看到许多新的引擎(如database.com、VoltDB、GenieDB等,见图2),它们提高了传统的解决方案、分片以及云计算方案的能力。这要感谢NoSQL运动。
但是随着众多的数据库尝试实现所有的特性,明确的边界消失了
确定使用哪种数据库比以前更为复杂了。
你必须要知道50个用例、50个数据库并要回答至少50个问题。关于后者,笔者在过去两年多的NoSQL咨询中进行了收集,可以在以下地址找到:选择正确的数据库,在NoSQL和NewSQL间进行选择。 - 一个通用的真理就是,每一项技术的变化——从客户端-服务端技术开始甚至更早——需要十倍的成本才能进行转移。例如,从大型机到客户端-服务端、客户端-服务端到SOA、SOA到WEB、RDBMS到混合型持久化之间的转换都是如此。所以可以推断出,在将NoSQL加入到他们的产品决策上,很多的公司在迟疑和纠结。但是,大家也都知道,最先采用的公司会从这个两个领域获益并且能够快速集成NoSQL,所以在将来会占据更有利的位置。就这一点而言,NoSQL解决方案会一直存在并且评估起来会是有利可图的领域。
关于作者
Prof. Dr. Stefan Edlich是德国柏林Beuth HS技术(University of App. Sc.)的高级讲师。他为诸多出版社如Apress、OReilly、Spektrum/Elsevier等编写了超过10本IT图书。他维护着NoSQL Archive网站, 从事NoSQL咨询并组织NoSQL技术会议,编写了世界上最早的两本NoSQL图书,现在他热衷于Clojure编程语言。
SQL调优01–SQL优化介绍
Introduction to SQL Tuning
- SQL效率太低的原因:
- Stale or missing optimizer statistics:缺失优化统计信息或者信息太旧;
- Missing access structures:缺少索引,考虑索引的效率;
- Suboptimal execution plan selection:不是最好的执行计划;
- Poorly constructed SQL:SQL语句写的不好;
- 最重要的是表的数据量太大,归档历史数据.小的数据量可以解决一切问题;
- 解决办法:
- 尽量不要用子查询,可以通过关联查询解决;
- 不要再表列上面使用函数,导致索引无效;
- 如果发生隐式转换也不走索引,因为oracle内部总是转换表的列;
- 尽量使用UNION ALL而不用UNION;
- 排序,去重复,分组现在默认使用hash去除重复,对CPU消耗很大;
- 性能监控的解决方案;
- Oracle中监控和调优的工具;
- 调优的工具:
- Automatic Database Diagnostic Monitor (ADDM);
- SQL Tuning Advisor;
- SQL Tuning Sets;
- SQL Access Advisor;
- SQL Performance Analyzer;
- SQL Monitoring;
- SQL Plan Management:在11g中的工具,可以控制某个sql的执行计划;
- SQL调优的任务:
- 查找高负载的SQL语句;
- 收集统计信息;
- 收集系统统计信息;
- 重建已存在的索引;
- 维护执行计划;
- 创建新的索引;
- CPU和Wait Time的调优
- db_time=cpu_time+wait_time;
- 如果db_time增加,cpu_time和wait_time等比例增加,说明这是一个可扩展的系统,只需增加硬件即可;
- 如果db_time增加,cpu_time远大于wait_time的增加,说明SQL效率不高,需要SQL的优化;
- 如果db_time增加,cpu_time远小于wait_time的增加,说明内部有争用或者IO效率太低;
- 客户系统的常见问题:
- Bad connection management:可以使用连接池解决;
- Bad use of cursors and the shared pool:适当调大SGA和PGA,并指定动态管理;
- Excess of resources consuming SQL statements:sql要反复执行;
- Use of nonstandard initialization parameters:使用了隐含参数或者参数使用不当;
- Poor database disk configuration:IO问题;
- Redo log setup problems:至少使用三组在线日志组,每组设置足够大,保证20分钟切换一次;
- Excessive serialization:串行化扫描,添加索引,尽量使用单列索引,可控性比较强;
- Inappropriate full table scans:全表扫描,主要是加索引解决;
- Large number of space-management or parse-related generated SQL statements:如果使用本地管理表空间的的话一般不会出现递归SQL;
- Deployment and migration errors:部署时出错,这个是人为原因;
- 应用的设计:
- 简化设计;
- 数据模型:
- 主要还是要与业务逻辑相结合;
- 可以使用建模工具如Oracle Designer,但是最好是使用详细的文档;
- 考虑是OLTP系统还是DW系统;
- 表设计:
- 考虑使用默认值,约束,物化视图,分区表等特性;
- 分区列一定要在where条件上,而且最好不要更新;
- 不建议使用触发器;,触发器的目的是做check,而不是做DML;
- 不建议使用外键,可以使用程序保证数据的完整性;
- 索引设计:
- 索引的列一定要经常出现在WHERE后面;
- 在DW中建议使用外键,在OLTP中考虑到性能可以不用外键,加外键的话就一定要加索引;
- 视图:
- 可以使用视图,但是最好不要嵌套视图;
- 嵌套视图影响执行计划;
- Share Cursors
- 尽量使用存储过程和函数;
- cursor_sharing初始化参数尽量不要修改;
SQL调优04–阅读执行计划
Interpreting Execution Plans
- 执行计划的解释:
- SQL语句的执行计划是由语句中行源的执行计划组成;
- 执行计划是使用父子关系来描述的,像一个树的结构;
- 如何查看执行计划:
- PLAN_TABLE:是由EXPLAIN PLAN命令或者SQL/PLUS的autotrace产生的执行计划,是理论上的执行计划;
- v$sql_plan:在Shared Pool中的Library Cache中保存的实际使用的执行计划;
- v$sql_plan_monitor:11g中的执行计划监控;
- dba_hist_sql_plan:由AWR报告产生的执行计划;
- stats$sql_plan:是由Statspack生成的执行计划;
- SQL Management Base:是由SQL Plan Management Baselines产生的执行计划;
- SQL tuning set;
- DBMS_MONITOR产生的trace文件:相当于10046事件;
- 由10053事件产生的trace文件;
- 10gR2之后的dump跟踪文件;
- 查看执行计划的视图:
- 如果直接查看基表的话,根本无法直接看到执行计划间的关系,自己编写SQL语句查看很麻烦,可以使用DBMS_XPLAN包下面的函数来完成;
- DBMS_XPLAN.DISPLAY():用来显示plan_table中的执行计划;
- DBMS_XPLAN.DISPLAY_CURSOR():用来显示v$sql_plan中的执行计划;
- DBMS_XPLAN.DISPLAY_AWR():用来显示AWR中的执行计划;
- DBMS_XPLAN.DISPLAY_SQLSET():用来显示SQL tuning set中的执行计划;
- DBMS_XPLAN.DISPLAY_SQL_PLAN_BASELINE():用来显示SQL Plan Management Baselines中的执行计划;
- EXPLAIN TABLE命令:
- 生成一个最优的执行计划,把它存在PLAN_TABLE中,但是并不实际执行SQL语句;
- 语法:EXPLAIN PLAN [SET STATEMENT_ID = ‘text’] [INTO plan_table] FOR statement;默认插入到PLAN_TABLE表中;
- PLAN_TABLE:
- 当执行EXPLAN_PLAN命令时自动创建PLAN_TABLE,它是一个同义词,指向sys.plan_table$的临时表;SELECT * FROM dba_synonyms WHERE synonym_name = ‘PLAN_TABLE’;SELECT table_name, TEMPORARY, duration FROM dba_tables WHERE table_name = ‘PLAN_TABLE$’;
- 可以根据$ORACLE_HOME/rdbms/admin/utlxplan.sql脚本创建自己的表,因为默认是临时表,只能在当前session查看,导入到自己的表中就可以永久保存;
- 优点是SQL语句么有真正执行;缺点是可能不是真正的执行计划,只有使用绑定变量时执行计划不准,其它情况都准确;
- 表中的内容是层级结构,可以通过ID和PAREANT_ID列来关联;
- 当执行EXPLAN_PLAN命令时自动创建PLAN_TABLE,它是一个同义词,指向sys.plan_table$的临时表;SELECT * FROM dba_synonyms WHERE synonym_name = ‘PLAN_TABLE’;SELECT table_name, TEMPORARY, duration FROM dba_tables WHERE table_name = ‘PLAN_TABLE$’;
- DBMS_XPLAN.DISPLAY函数语法:DBMS_XPLAN.DISPLAY(table_name, statement_id, format, filter_preds):
- table_name:默认是PLAN_TABLE表;
- statement_id:默认是空,可以根据这个参数获得指定的语句的执行计划;
- format:默认是TYPICAL类型,其他类型查帮助文档,显示的信息多少;
- 默认只查看上一条语句的执行计划;
- 查看指定statement_id的执行计划;
- 查看更多的执行计划的信息;
- AUTOTRACE:
- AUTOTRACE是sql*plus的功能,在oracle7.3版本后出现,也是把记录存放在PLAN_TABLE表中;
- 需要PLUSTRACE角色从v$视图中检索统计信息,使用$ORACLE_HOME/sqlplus/admin/plustrce.sql脚本创建;
- 默认情况下,在执行完查询语句后会生成执行计划和统计信息;
- 相当于执行了一次EXPLAIN PLAN命令然后执行了一次语句,如果使用绑定变量的话可能不是真实的计划;
- 语法:SET AUTOT[RACE] {OFF | ON | TRACE[ONLY]} [EXP[LAIN]] [STAT[ISTICS]];
- ON:要显示结果和trace信息;
- TRACEONLY:不显示结果;
- 查看当前的设置:show autotrace;
- 阅读统计信息:
- recursive calls:递归的调用,读取数据字典,权限,列的信息.第一次执行会很大,以后执行会变小;如果使用存储过的话,这个值一般会很大,属于正常;可以通过清除shared_pool测试:alter system flush shared_pool;
- db block gets:修改当前状态的数据块的block的块数.只有当DML语句会引起db block gets增加,因为当前块会被更新,SELECT语句的话不会增加,因为可以读取REDO或者构造的CR块;
- consistent gets:逻辑读的数量(不是BLOCK),表示返回记录的批次数,跟当前的arraysize有关;
- arraysize:表示一次返回的记录数,通过show arraysize命令查看;
- 粗略是算法是:consistent gets=rows processed/arraysize,记录越多越接近;
- 优化时应该关心在相同的arraysize下减小此值,即减小逻辑读;
- physical reads:物理读,即从硬盘读取的BLOCK的数量,BUFFER CACHE越大这个值越小,可以通过清除BUFFER CACHE测试:alter system flush buffer_cache;
- redo size:产生的日志的数量,一般DML语句才会产生;
- bytes sent via SQL*Net to client:服务器发送到客户端的字节数;
- bytes received via SQL*Net from client:服务器接收到客户端的字节数;
- SQL*Net roundtrips to/from client:SQL的网络流量的次数,也跟arraysize参数有关;
- sorts (memory):内存中的排序数量,主要是PGA;
- sorts (disk):在硬盘的排序,应该避免这个值;
- rows processed:处理的记录数;
- v$sql_plan:
- v$sql_plan:查看library cahce中真正使用的执行计划;PLAN_TABLE只是理论上的执行计划;
- 可以通过sql_id列与v$sql表关联,也可以使用address和hash_value的值;
- 主要的列:
- HASH_VALUE:父语句在library cache中的哈希值;
- ADDRESS:访问SQL语句的句柄,即内存地址;
- CHILD_NUMBER:使用此执行计划的子CURSOR数量;
- POSITION:具有相同PARENT_ID的操作的执行顺序;
- PARENT_ID:跳出过程的下一个执行的过程ID,这个很抽象,看到执行计划,很容易理解这一点;
- ID:每一个步骤的编号;
- PLAN_HASH_VALUE:执行计划的哈希值;
- 查看实际的执行计划:SELECT * FROM table(DBMS_XPLAN.DISPLAY_CURSOR(‘sql_id’));
- v$sql_plan_statistics:提供实际执行时的统计信息
- 当STATISTICS_LEVEL设置为ALL时才会收集;
- 或者语句中指定了GATHER_PLAN_STATISTICS的hint;
- v$sql_plan_statistics_all:获得所有的实际执行的统计信息;
- v$sql_workarea:提供了SQL CURSOR使用的工作区的信息;
- AWR:
- AWR是为了检测和自调整为目的的收集,处理,维护性能统计信息;
- 统计信息包括:
- 对象统计信息;
- 时间模型统计信息;
- 一些系统和session的统计信息;
- ASH(Active Session History)统计信息;
- 自动生成性能数据的快照;
- 重要的AWR视图:
- V$ACTIVE_SESSION_HISTORY;
- V$metric views;
- DBA_HIST views:
- DBA_HIST_ACTIVE_SESS_HISTORY;
- DBA_HIST_BASELINE;
- DBA_HIST_DATABASE_INSTANCE;
- DBA_HIST_SNAPSHOT;
- DBA_HIST_SQL_PLAN;
- DBA_HIST_WR_CONTROL;
- 指定sql_id查看AWR中的sql的执行计划: SELECT plan_table_output FROM TABLE(DBMS_XPLAN.DISPLAY_AWR(‘g22czkqq3pxmb’));
- 从AWR数据生成一个SQL报告:@$ORACLE_HOME/rdbms/admin/awrsqrpt;
- SQL Monitoring:11g;
- 阅读执行计划:
- 读执行计划的顺序:
- 从上往下看,第一个没有儿子节点的节点最先执行;
- 执行执行其兄弟节点;
- 最后执行父节点;
- 就是二叉树中的后序遍历的方式:
- 前序遍历:对任一子树,先访问根,然后遍历其左子树,最后遍历其右子树;
- 中序遍历:对任一子树,先遍历其左子树,然后访问根,最后遍历其右子树;
- 后序遍历:对任一子树,先遍历其左子树,然后遍历其右子树,最后访问根;
- 例子:
- 执行的顺序为:356421;
- 执行的顺序为:43652871;
- 执行顺序为:325410;
- 执行的顺序为:356421;
- 查看执行计划的建议:
- 要使驱动表保持最好的过滤条件,即驱动表有最小的记录;
- 每一步返回的数据尽量最小;
- 正确使用视图,只是用一层,尽量不要嵌套;
- 避免使用笛卡尔积;
- 读执行计划的顺序:
- 仅仅靠一个执行计划不能说明它是否是最好的,可以借助SQL Tuning Advisor工具;