# SCAN
**SCAN cursor [MATCH pattern] [COUNT count]**
[_SCAN_](#scan) 命令及其相關的 [_SSCAN_](../set/sscan.html#sscan) 命令、 [_HSCAN_](../hash/hscan.html#hscan) 命令和 [_ZSCAN_](../sorted_set/zscan.html#zscan) 命令都用于增量地迭代(incrementally iterate)一集元素(a collection of elements):
* [_SCAN_](#scan) 命令用于迭代當前數據庫中的數據庫鍵。
* [_SSCAN_](../set/sscan.html#sscan) 命令用于迭代集合鍵中的元素。
* [_HSCAN_](../hash/hscan.html#hscan) 命令用于迭代哈希鍵中的鍵值對。
* [_ZSCAN_](../sorted_set/zscan.html#zscan) 命令用于迭代有序集合中的元素(包括元素成員和元素分值)。
以上列出的四個命令都支持增量式迭代, 它們每次執行都只會返回少量元素, 所以這些命令可以用于生產環境, 而不會出現像 [_KEYS_](keys.html#keys) 命令、 [_SMEMBERS_](../set/smembers.html#smembers) 命令帶來的問題 —— 當 [_KEYS_](keys.html#keys) 命令被用于處理一個大的數據庫時, 又或者 [_SMEMBERS_](../set/smembers.html#smembers) 命令被用于處理一個大的集合鍵時, 它們可能會阻塞服務器達數秒之久。
不過, 增量式迭代命令也不是沒有缺點的: 舉個例子, 使用 [_SMEMBERS_](../set/smembers.html#smembers) 命令可以返回集合鍵當前包含的所有元素, 但是對于 [_SCAN_](#scan) 這類增量式迭代命令來說, 因為在對鍵進行增量式迭代的過程中, 鍵可能會被修改, 所以增量式迭代命令只能對被返回的元素提供有限的保證 (offer limited guarantees about the returned elements)。
因為 [_SCAN_](#scan) 、 [_SSCAN_](../set/sscan.html#sscan) 、 [_HSCAN_](../hash/hscan.html#hscan) 和 [_ZSCAN_](../sorted_set/zscan.html#zscan) 四個命令的工作方式都非常相似, 所以這個文檔會一并介紹這四個命令, 但是要記住:
* [_SSCAN_](../set/sscan.html#sscan) 命令、 [_HSCAN_](../hash/hscan.html#hscan) 命令和 [_ZSCAN_](../sorted_set/zscan.html#zscan) 命令的第一個參數總是一個數據庫鍵。
* 而 [_SCAN_](#scan) 命令則不需要在第一個參數提供任何數據庫鍵 —— 因為它迭代的是當前數據庫中的所有數據庫鍵。
## SCAN 命令的基本用法
[_SCAN_](#scan) 命令是一個基于游標的迭代器(cursor based iterator): [_SCAN_](#scan) 命令每次被調用之后, 都會向用戶返回一個新的游標, 用戶在下次迭代時需要使用這個新游標作為 [_SCAN_](#scan) 命令的游標參數, 以此來延續之前的迭代過程。
當 [_SCAN_](#scan) 命令的游標參數被設置為 `0` 時, 服務器將開始一次新的迭代, 而當服務器向用戶返回值為 `0` 的游標時, 表示迭代已結束。
以下是一個 [_SCAN_](#scan) 命令的迭代過程示例:
```
redis 127.0.0.1:6379> scan 0
1) "17"
2) 1) "key:12"
2) "key:8"
3) "key:4"
4) "key:14"
5) "key:16"
6) "key:17"
7) "key:15"
8) "key:10"
9) "key:3"
10) "key:7"
11) "key:1"
redis 127.0.0.1:6379> scan 17
1) "0"
2) 1) "key:5"
2) "key:18"
3) "key:0"
4) "key:2"
5) "key:19"
6) "key:13"
7) "key:6"
8) "key:9"
9) "key:11"
```
在上面這個例子中, 第一次迭代使用 `0` 作為游標, 表示開始一次新的迭代。
第二次迭代使用的是第一次迭代時返回的游標, 也即是命令回復第一個元素的值 —— `17` 。
從上面的示例可以看到, [_SCAN_](#scan) 命令的回復是一個包含兩個元素的數組, 第一個數組元素是用于進行下一次迭代的新游標, 而第二個數組元素則是一個數組, 這個數組中包含了所有被迭代的元素。
在第二次調用 [_SCAN_](#scan) 命令時, 命令返回了游標 `0` , 這表示迭代已經結束, 整個數據集(collection)已經被完整遍歷過了。
以 `0` 作為游標開始一次新的迭代, 一直調用 [_SCAN_](#scan) 命令, 直到命令返回游標 `0` , 我們稱這個過程為一次**完整遍歷**(full iteration)。
## SCAN 命令的保證(guarantees)
[_SCAN_](#scan) 命令, 以及其他增量式迭代命令, 在進行完整遍歷的情況下可以為用戶帶來以下保證: 從完整遍歷開始直到完整遍歷結束期間, 一直存在于數據集內的所有元素都會被完整遍歷返回; 這意味著, 如果有一個元素, 它從遍歷開始直到遍歷結束期間都存在于被遍歷的數據集當中, 那么 [_SCAN_](#scan) 命令總會在某次迭代中將這個元素返回給用戶。
然而因為增量式命令僅僅使用游標來記錄迭代狀態, 所以這些命令帶有以下缺點:
* 同一個元素可能會被返回多次。 處理重復元素的工作交由應用程序負責, 比如說, 可以考慮將迭代返回的元素僅僅用于可以安全地重復執行多次的操作上。
* 如果一個元素是在迭代過程中被添加到數據集的, 又或者是在迭代過程中從數據集中被刪除的, 那么這個元素可能會被返回, 也可能不會, 這是未定義的(undefined)。
## SCAN 命令每次執行返回的元素數量
增量式迭代命令并不保證每次執行都返回某個給定數量的元素。
增量式命令甚至可能會返回零個元素, 但只要命令返回的游標不是 `0` , 應用程序就不應該將迭代視作結束。
不過命令返回的元素數量總是符合一定規則的, 在實際中:
* 對于一個大數據集來說, 增量式迭代命令每次最多可能會返回數十個元素;
* 而對于一個足夠小的數據集來說, 如果這個數據集的底層表示為編碼數據結構(encoded data structure,適用于是小集合鍵、小哈希鍵和小有序集合鍵), 那么增量迭代命令將在一次調用中返回數據集中的所有元素。
最后, 用戶可以通過增量式迭代命令提供的 `COUNT` 選項來指定每次迭代返回元素的最大值。
## COUNT 選項
雖然增量式迭代命令不保證每次迭代所返回的元素數量, 但我們可以使用 `COUNT` 選項, 對命令的行為進行一定程度上的調整。
基本上, `COUNT` 選項的作用就是讓用戶告知迭代命令, 在每次迭代中應該從數據集里返回多少元素。
雖然 `COUNT` 選項**只是對增量式迭代命令的一種提示**(hint), 但是在大多數情況下, 這種提示都是有效的。
* `COUNT` 參數的默認值為 `10` 。
* 在迭代一個足夠大的、由哈希表實現的數據庫、集合鍵、哈希鍵或者有序集合鍵時, 如果用戶沒有使用 `MATCH` 選項, 那么命令返回的元素數量通常和 `COUNT` 選項指定的一樣, 或者比 `COUNT` 選項指定的數量稍多一些。
* 在迭代一個編碼為整數集合(intset,一個只由整數值構成的小集合)、 或者編碼為壓縮列表(ziplist,由不同值構成的一個小哈希或者一個小有序集合)時, 增量式迭代命令通常會無視 `COUNT` 選項指定的值, 在第一次迭代就將數據集包含的所有元素都返回給用戶。
Note
**并非每次迭代都要使用相同的** `COUNT` **值。**
用戶可以在每次迭代中按自己的需要隨意改變 `COUNT` 值, 只要記得將上次迭代返回的游標用到下次迭代里面就可以了。
## MATCH 選項
和 [_KEYS_](keys.html#keys) 命令一樣, 增量式迭代命令也可以通過提供一個 glob 風格的模式參數, 讓命令只返回和給定模式相匹配的元素, 這一點可以通過在執行增量式迭代命令時, 通過給定 `MATCH <pattern>` 參數來實現。
以下是一個使用 `MATCH` 選項進行迭代的示例:
```
redis 127.0.0.1:6379> sadd myset 1 2 3 foo foobar feelsgood
(integer) 6
redis 127.0.0.1:6379> sscan myset 0 match f*
1) "0"
2) 1) "foo"
2) "feelsgood"
3) "foobar"
```
需要注意的是, 對元素的模式匹配工作是在命令從數據集中取出元素之后, 向客戶端返回元素之前的這段時間內進行的, 所以如果被迭代的數據集中只有少量元素和模式相匹配, 那么迭代命令或許會在多次執行中都不返回任何元素。
以下是這種情況的一個例子:
```
redis 127.0.0.1:6379> scan 0 MATCH *11*
1) "288"
2) 1) "key:911"
redis 127.0.0.1:6379> scan 288 MATCH *11*
1) "224"
2) (empty list or set)
redis 127.0.0.1:6379> scan 224 MATCH *11*
1) "80"
2) (empty list or set)
redis 127.0.0.1:6379> scan 80 MATCH *11*
1) "176"
2) (empty list or set)
redis 127.0.0.1:6379> scan 176 MATCH *11* COUNT 1000
1) "0"
2) 1) "key:611"
2) "key:711"
3) "key:118"
4) "key:117"
5) "key:311"
6) "key:112"
7) "key:111"
8) "key:110"
9) "key:113"
10) "key:211"
11) "key:411"
12) "key:115"
13) "key:116"
14) "key:114"
15) "key:119"
16) "key:811"
17) "key:511"
18) "key:11"
```
如你所見, 以上的大部分迭代都不返回任何元素。
在最后一次迭代, 我們通過將 `COUNT` 選項的參數設置為 `1000` , 強制命令為本次迭代掃描更多元素, 從而使得命令返回的元素也變多了。
## 并發執行多個迭代
在同一時間, 可以有任意多個客戶端對同一數據集進行迭代, 客戶端每次執行迭代都需要傳入一個游標, 并在迭代執行之后獲得一個新的游標, 而這個游標就包含了迭代的所有狀態, 因此, 服務器無須為迭代記錄任何狀態。
## 中途停止迭代
因為迭代的所有狀態都保存在游標里面, 而服務器無須為迭代保存任何狀態, 所以客戶端可以在中途停止一個迭代, 而無須對服務器進行任何通知。
即使有任意數量的迭代在中途停止, 也不會產生任何問題。
## 使用錯誤的游標進行增量式迭代
使用間斷的(broken)、負數、超出范圍或者其他非正常的游標來執行增量式迭代并不會造成服務器崩潰, 但可能會讓命令產生未定義的行為。
未定義行為指的是, 增量式命令對返回值所做的保證可能會不再為真。
只有兩種游標是合法的:
1. 在開始一個新的迭代時, 游標必須為 `0` 。
2. 增量式迭代命令在執行之后返回的, 用于延續(continue)迭代過程的游標。
## 迭代終結的保證
增量式迭代命令所使用的算法只保證在數據集的大小有界(bounded)的情況下, 迭代才會停止, 換句話說, 如果被迭代數據集的大小不斷地增長的話, 增量式迭代命令可能永遠也無法完成一次完整迭代。
從直覺上可以看出, 當一個數據集不斷地變大時, 想要訪問這個數據集中的所有元素就需要做越來越多的工作, 能否結束一個迭代取決于用戶執行迭代的速度是否比數據集增長的速度更快。
**可用版本:**
> >= 2.8.0
**時間復雜度:**
> 增量式迭代命令每次執行的復雜度為 O(1) , 對數據集進行一次完整迭代的復雜度為 O(N) , 其中 N 為數據集中的元素數量。
**返回值:**
> [_SCAN_](#scan) 命令、 [_SSCAN_](../set/sscan.html#sscan) 命令、 [_HSCAN_](../hash/hscan.html#hscan) 命令和 [_ZSCAN_](../sorted_set/zscan.html#zscan) 命令都返回一個包含兩個元素的 multi-bulk 回復: 回復的第一個元素是字符串表示的無符號 64 位整數(游標), 回復的第二個元素是另一個 multi-bulk 回復, 這個 multi-bulk 回復包含了本次被迭代的元素。
>
> [_SCAN_](#scan) 命令返回的每個元素都是一個數據庫鍵。
>
> [_SSCAN_](../set/sscan.html#sscan) 命令返回的每個元素都是一個集合成員。
>
> [_HSCAN_](../hash/hscan.html#hscan) 命令返回的每個元素都是一個鍵值對,一個鍵值對由一個鍵和一個值組成。
>
> [_ZSCAN_](../sorted_set/zscan.html#zscan) 命令返回的每個元素都是一個有序集合元素,一個有序集合元素由一個成員(member)和一個分值(score)組成。
- Redis 教程
- Redis 簡介
- Redis 安裝
- Redis 配置
- Redis 數據類型
- Redis 命令
- Redis 數據備份與恢復
- Redis 安全
- Redis 性能測試
- Redis 客戶端連接
- Redis 管道技術
- Redis 分區
- Java 使用 Redis
- Java 使用 Redis
- PHP 使用 Redis
- PHP 使用 Redis
- Redis 命令參考
- Key(鍵)
- DEL
- DUMP
- EXISTS
- EXPIRE
- EXPIREAT
- KEYS
- MIGRATE
- MOVE
- OBJECT
- PERSIST
- PEXPIRE
- PEXPIREAT
- PTTL
- RANDOMKEY
- RENAME
- RENAMENX
- RESTORE
- SORT
- TYPE
- SCAN
- String(字符串)
- APPEND
- BITCOUNT
- BITOP
- DECR
- DECRBY
- GET
- GETBIT
- GETRANGE
- GETSET
- INCR
- INCRBY
- INCRBYFLOAT
- MGET
- MSET
- MSETNX
- PSETEX
- SET
- SETBIT
- SETEX
- SETNX
- SETRANGE
- STRLEN
- Hash(哈希表)
- HDEL
- HEXISTS
- HGET
- HGETALL
- HINCRBY
- HINCRBYFLOAT
- HKEYS
- HLEN
- HMGET
- HMSET
- HSET
- HSETNX
- HVALS
- HSCAN
- List(列表)
- BLPOP
- BRPOP
- BRPOPLPUSH
- LINDEX
- LINSERT
- LLEN
- LPOP
- LPUSH
- LRANGE
- LREM
- LSET
- LTRIM
- RPOP
- RPOPLPUSH
- RPUSH
- RPUSHX
- Set(集合)
- SADD
- SCARD
- SDIFF
- SDIFFSTORE
- SINTER
- SINTER
- SINTERSTORE
- SISMEMBER
- SMEMBERS
- SMOVE
- SPOP
- SRANDMEMBER
- SREM
- SUNION
- SUNIONSTORE
- SSCAN
- SortedSet(有序集合)
- ZADD
- ZCARD
- ZCOUNT
- ZINCRBY
- ZRANGE
- ZRANGEBYSCORE
- ZRANK
- ZREM
- ZREMRANGEBYRANK
- ZREMRANGEBYSCORE
- ZREVRANGE
- ZREVRANGEBYSCORE
- ZREVRANK
- ZSCORE
- ZUNIONSTORE
- ZINTERSTORE
- ZSCAN
- Pub/Sub(發布/訂閱)
- PSUBSCRIBE
- PUBLISH
- PUBSUB
- PUNSUBSCRIBE
- SUBSCRIBE
- UNSUBSCRIBE
- Transaction(事務)
- DISCARD
- EXEC
- MULTI
- UNWATCH
- WATCH
- Script(腳本)
- EVAL
- EVALSHA
- SCRIPT EXISTS
- SCRIPT FLUSH
- SCRIPT KILL
- SCRIPT LOAD
- Connection(連接)
- AUTH
- ECHO
- PING
- QUIT
- SELECT
- Server(服務器)
- BGREWRITEAOF
- BGSAVE
- CLIENT GETNAME
- CLIENT KILL
- CLIENT LIST
- CLIENT SETNAME
- CONFIG GET
- CONFIG RESETSTAT
- CONFIG REWRITE
- CONFIG SET
- DBSIZE
- DEBUG OBJECT
- DEBUG SEGFAULT
- FLUSHALL
- FLUSHDB
- INFO
- LASTSAVE
- MONITOR
- PSYNC
- SAVE
- SHUTDOWN
- SLAVEOF
- SLOWLOG
- SYNC
- TIME
- 免責聲明