SCAN 为什么不会一次阻塞很久?它到底保证什么、不保证什么?这篇在 100 万个 key 的库上把它的耗时、遍历时库在变化的行为、以及 MATCH 的过滤时机都量出来。
前 8 次调用的 (传入游标, 返回 key 数): [('0', 11), ('589824', 10), ('98304', 11), ('491520', 10), ('212992', 11), ('442368', 11), ('598016', 10), ('565248', 10)]
一次完整遍历(COUNT 10 时前 8 次的游标值)是: ['0', '589824', '98304', '491520', '212992', '442368', '598016', '565248'] ——不是 0,10,20…,而是按「二进制位倒序」推进每次返回的游标不是「偏移量」,前几次依次是 0、720896、425984、671744……;返回的 key 数也不严格等于 COUNT(11、10、12、10……,COUNT 只是提示)。调用方唯一要做的是:把返回的游标原样传给下一次调用,直到游标是 0。
命令 单次耗时 ms 完成一轮遍历需要调用次数 另一个连接 PING 最大停顿 ms SCAN COUNT 10 0.038 95445 (未测) SCAN COUNT 100 0.105 9952 (未测) SCAN COUNT 1000 0.782 1000 1.11 SCAN COUNT 10000 7.113 100 12.59 KEYS * 660.7 1 94.87
单次耗时随 COUNT 线性增长;COUNT 1000 时 0.78 ms,期间另一个连接的 PING 最大停顿 1.1 ms。KEYS * 一次性遍历整个库:661 ms(含传输),PING 被卡 95 ms(第 119 篇专门量 KEYS 的风险)。COUNT 不宜太大:COUNT 10000 时单次 7.1 ms,PING 停顿已经到 12.6 ms。
开始前的 20 万个 a:* key:遍历到 200000 个(应为 200000);遍历过程中新增了 400000 个 b:* key,遍历到其中 386583 个;返回的 key 总数 586583,其中重复 0 个
SCAN 的保证是:从开始到结束一直存在的 key 一定会被返回至少一次。实测边遍历边新增 40 万个 key(库从 20 万涨到 60 万,字典会扩容),开始前的 20 万个 key 一个不少,新增的 key 只有 386583 个被遍历到——新增的不保证出现。文档还说 SCAN 可能返回重复的 key,这次实验里重复是 0 个;所以调用方仍然要做好去重的准备。
50 万个 key、只有 2 个匹配:遍历完需要 500 次调用,其中 498 次返回空数组,共找到 2 个
MATCH 不会加速遍历:服务器先按游标取出一批,再对这一批过滤。匹配很少时,大部分调用返回空数组(500 次里 498 次)。判断遍历是否结束只能看游标是否为 0,不能看返回数组是否为空。