自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Redis 分頁(yè) + 多條件模糊查詢太頭疼?這套方案幫你輕松搞定!

數(shù)據(jù)庫(kù) Redis
根據(jù)不同的業(yè)務(wù)場(chǎng)景選擇合適的數(shù)據(jù)結(jié)構(gòu),比如有序集合適合需要排序和范圍查詢的場(chǎng)景,集合適合需要去重和交集、并集操作的場(chǎng)景,字符串適合存儲(chǔ)單個(gè)對(duì)象的詳細(xì)信息。

我猜不少搞 Java 開發(fā)的兄弟,在項(xiàng)目里碰到 Redis 分頁(yè)和多條件模糊查詢的時(shí)候,都跟我一樣,心里直犯嘀咕:"這玩意兒咋整?。空瓦@么難搞呢?" 別慌,今兒個(gè)咱就來(lái)好好嘮嘮,怎么把這倆難題輕松搞定,讓你在同事面前狠狠露一手!

一、先搞明白為啥 Redis 分頁(yè)和多條件模糊查詢讓人頭大

咱先說說 Redis 分頁(yè)。用過 Redis 的都知道,它和咱們熟悉的 MySQL 這些關(guān)系型數(shù)據(jù)庫(kù)不一樣。MySQL 里有個(gè) LIMIT 關(guān)鍵字,分頁(yè)查詢那叫一個(gè)方便,直接就能指定查第幾頁(yè)、每頁(yè)多少條???Redis 呢,它主要是基于內(nèi)存的鍵值對(duì)存儲(chǔ),數(shù)據(jù)結(jié)構(gòu)雖然豐富,但原生就沒有像數(shù)據(jù)庫(kù)那樣專門的分頁(yè)功能。

你要是存的數(shù)據(jù)是放在列表(List)里,想分頁(yè)的話,可能得用 LRANGE 命令。比如說列表鍵是 users,想查第 1 頁(yè),每頁(yè) 10 條,就用 LRANGE users 0 9。乍一看好像還行,可要是列表里的數(shù)據(jù)是動(dòng)態(tài)變化的,比如經(jīng)常有新增、刪除操作,列表里元素的位置就會(huì)變,這時(shí)候用 LRANGE 分頁(yè),結(jié)果可能就不準(zhǔn)確了。而且要是列表特別大,每次用 LRANGE 都得遍歷一堆元素,性能也會(huì)受影響。

再看看多條件模糊查詢。Redis 本身的查詢能力比較有限,不像數(shù)據(jù)庫(kù)能支持復(fù)雜的 SQL 語(yǔ)句,什么 LIKE 啊、多個(gè)條件組合啊都能輕松搞定。Redis 里的鍵匹配,一般就靠 KEYS 命令或者 SCAN 命令。KEYS 命令能根據(jù)通配符匹配鍵,比如 KEYS user:* 能查出所有以 user: 開頭的鍵,可這玩意兒有個(gè)大問題,它是全量掃描,在生產(chǎn)環(huán)境用的話,要是鍵的數(shù)量特別多,會(huì)把 Redis 搞得很慢,甚至卡住。

SCAN 命令雖然能增量掃描,避免全量掃描的問題,但它返回的只是鍵,要是你想根據(jù)鍵對(duì)應(yīng)的值里的多個(gè)條件進(jìn)行模糊查詢,比如用戶表里要根據(jù)用戶名包含 "張三",年齡在 20 到 30 之間來(lái)查詢,SCAN 就沒辦法直接做到了,你得把鍵對(duì)應(yīng)的所有值都取出來(lái),在應(yīng)用層進(jìn)行過濾,這就會(huì)增加應(yīng)用服務(wù)器的負(fù)擔(dān),而且效率也不高。

舉個(gè)簡(jiǎn)單的例子,假設(shè)咱們有個(gè)電商項(xiàng)目,要在 Redis 里存儲(chǔ)商品信息,每個(gè)商品的鍵是 product:1、product:2 這樣的形式,值是 JSON 格式,包含商品名稱、價(jià)格、類別等信息?,F(xiàn)在要查詢名稱里包含 "手機(jī)",價(jià)格在 2000 到 4000 之間的商品,并且要分頁(yè)顯示。這時(shí)候問題就來(lái)了,怎么根據(jù)商品名稱和價(jià)格這兩個(gè)條件來(lái)查詢呢?直接用 Redis 原生的功能很難實(shí)現(xiàn),這就需要咱們想辦法來(lái)解決。

二、搞定 Redis 分頁(yè)的實(shí)用方案

(一)基于有序集合(Sorted Set)的分頁(yè)方案

有序集合是 Redis 里一個(gè)很強(qiáng)大的數(shù)據(jù)結(jié)構(gòu),它每個(gè)元素都有一個(gè)分?jǐn)?shù)(score),可以根據(jù)分?jǐn)?shù)對(duì)元素進(jìn)行排序。咱們可以利用這個(gè)特性來(lái)實(shí)現(xiàn)分頁(yè)。

比如說,咱們還是以用戶數(shù)據(jù)為例,每個(gè)用戶有一個(gè)唯一的 ID,咱們可以把用戶 ID 作為有序集合的成員,把用戶的創(chuàng)建時(shí)間作為分?jǐn)?shù)。這樣有序集合里的元素就是按照創(chuàng)建時(shí)間排序的。

要實(shí)現(xiàn)分頁(yè)查詢,假設(shè)每頁(yè)顯示 n 條數(shù)據(jù),第 m 頁(yè)的起始索引就是 (m - 1) * n,結(jié)束索引就是 m * n - 1。然后用 ZRANGE 命令來(lái)獲取指定范圍內(nèi)的成員。比如有序集合鍵是 users_sorted,查第 1 頁(yè),每頁(yè) 10 條,就是 ZRANGE users_sorted 0 9。

但是這里有個(gè)問題,如果用戶數(shù)據(jù)是不斷更新的,比如有用戶刪除了,有序集合里的元素?cái)?shù)量會(huì)減少,這時(shí)候原來(lái)的索引就會(huì)發(fā)生變化。不過對(duì)于大部分分頁(yè)場(chǎng)景來(lái)說,只要不是頻繁刪除中間的元素,這種方案還是比較可行的。

(二)記錄上一頁(yè)最后一個(gè)元素的分頁(yè)方案

這種方案適合數(shù)據(jù)是按照一定順序排列的情況,比如時(shí)間順序。咱們?cè)诓樵兩弦豁?yè)數(shù)據(jù)的時(shí)候,記錄下最后一個(gè)元素的相關(guān)信息,比如時(shí)間戳或者 ID,然后在下一頁(yè)查詢時(shí),根據(jù)這個(gè)信息來(lái)獲取下一頁(yè)的數(shù)據(jù)。

比如,咱們還是以按創(chuàng)建時(shí)間排序的用戶數(shù)據(jù)為例,假設(shè)上一頁(yè)最后一個(gè)用戶的創(chuàng)建時(shí)間是 last_score,那么下一頁(yè)查詢的時(shí)候,就可以用 ZRANGEBYSCORE 命令,從 last_score 之后開始獲取數(shù)據(jù)。命令大概是這樣的:ZRANGEBYSCORE users_sorted (last_score 0 9,這里的 (last_score 表示不包含 last_score 這個(gè)分?jǐn)?shù)的元素,然后獲取 10 條數(shù)據(jù)。

這種方案的好處是可以避免因?yàn)橹虚g元素刪除導(dǎo)致索引變化的問題,而且每次查詢的時(shí)間復(fù)雜度比較低,適合大數(shù)據(jù)量的分頁(yè)場(chǎng)景。

三、解決 Redis 多條件模糊查詢的巧妙辦法

(一)預(yù)處理數(shù)據(jù),建立多個(gè)索引

既然 Redis 原生不支持多條件模糊查詢,那咱們可以在數(shù)據(jù)寫入 Redis 的時(shí)候,對(duì)數(shù)據(jù)進(jìn)行預(yù)處理,根據(jù)不同的查詢條件建立索引。

還是以電商商品為例,商品有名稱、價(jià)格、類別等屬性。咱們可以建立三個(gè)有序集合:

  • 以商品名稱為索引的有序集合 product_name_index,成員是商品 ID,分?jǐn)?shù)可以是商品名稱的某種哈希值或者直接是名稱的拼音首字母(方便模糊查詢)。
  • 以價(jià)格為索引的有序集合 product_price_index,成員是商品 ID,分?jǐn)?shù)就是商品的價(jià)格。
  • 以類別為索引的有序集合 product_category_index,成員是商品 ID,分?jǐn)?shù)可以是類別 ID。

當(dāng)要進(jìn)行多條件模糊查詢時(shí),比如查詢名稱包含 "手機(jī)",價(jià)格在 2000 到 4000 之間的商品,咱們可以先根據(jù)名稱條件,從 product_name_index 中獲取所有名稱包含 "手機(jī)" 的商品 ID 集合,再?gòu)?product_price_index 中獲取價(jià)格在 2000 到 4000 之間的商品 ID 集合,然后對(duì)這兩個(gè)集合取交集,得到同時(shí)滿足這兩個(gè)條件的商品 ID,最后根據(jù)這些商品 ID 去獲取具體的商品信息。

對(duì)于模糊查詢名稱包含 "手機(jī)",咱們可以在建立索引的時(shí)候,把商品名稱的所有可能的子串都作為索引的一部分,或者使用一些模糊匹配的算法,比如編輯距離算法,不過這可能會(huì)增加索引的存儲(chǔ)量。更簡(jiǎn)單的辦法是,在應(yīng)用層對(duì)輸入的模糊查詢關(guān)鍵詞進(jìn)行處理,生成對(duì)應(yīng)的通配符模式,然后在 Redis 中使用 SCAN 命令結(jié)合鍵的模式來(lái)獲取相關(guān)的索引鍵,再獲取對(duì)應(yīng)的商品 ID 集合。

(二)使用 Redis 的位圖(Bitmap)

位圖可以用來(lái)表示某個(gè)元素是否存在,或者某個(gè)條件是否滿足。比如對(duì)于每個(gè)商品,我們可以用不同的位圖來(lái)表示不同的條件,比如價(jià)格是否在某個(gè)區(qū)間,類別是否屬于某一類等。

不過位圖在多條件查詢中的應(yīng)用相對(duì)比較復(fù)雜,需要結(jié)合其他數(shù)據(jù)結(jié)構(gòu)一起使用,這里咱們先重點(diǎn)介紹前面的索引方案。

四、綜合方案:讓分頁(yè)和多條件模糊查詢無(wú)縫結(jié)合

現(xiàn)在咱們把分頁(yè)和多條件模糊查詢結(jié)合起來(lái),看看怎么在實(shí)際場(chǎng)景中應(yīng)用。

假設(shè)咱們還是那個(gè)電商項(xiàng)目,要實(shí)現(xiàn)根據(jù)商品名稱模糊查詢、價(jià)格范圍查詢,并且進(jìn)行分頁(yè)顯示的功能。具體步驟如下:

(一)數(shù)據(jù)寫入階段

  1. 當(dāng)新增一個(gè)商品時(shí),首先生成一個(gè)唯一的商品 ID,比如 product:1001。
  2. 將商品的詳細(xì)信息以 JSON 格式存儲(chǔ)在 Redis 的字符串鍵中,鍵為 product:1001,值為 {"name":"華為手機(jī)", "price":3000, "category":"電子產(chǎn)品", "other_info":"..."}。
  3. 建立名稱索引:將商品名稱進(jìn)行處理,比如提取所有可能包含的關(guān)鍵詞,這里假設(shè)我們簡(jiǎn)單地將整個(gè)名稱作為索引的一部分,在有序集合 product_name_index 中,以商品 ID 為成員,以名稱的拼音或者某種可以用于模糊查詢的標(biāo)識(shí)為分?jǐn)?shù)(這里為了方便,暫時(shí)以名稱本身作為分?jǐn)?shù),實(shí)際項(xiàng)目中可能需要更復(fù)雜的處理)。比如 ZADD product_name_index "華為手機(jī)" "product:1001"。
  4. 建立價(jià)格索引:在有序集合 product_price_index 中,以商品 ID 為成員,價(jià)格為分?jǐn)?shù),執(zhí)行 ZADD product_price_index 3000 "product:1001"。
  5. 建立類別索引:在有序集合 product_category_index 中,以商品 ID 為成員,類別 ID 或者類別名稱為分?jǐn)?shù),假設(shè)類別是 "電子產(chǎn)品",執(zhí)行 ZADD product_category_index "電子產(chǎn)品" "product:1001"。

(二)查詢階段

當(dāng)用戶輸入查詢條件,比如名稱包含 "手機(jī)",價(jià)格在 2000 到 4000 之間,要查詢第 2 頁(yè),每頁(yè) 10 條數(shù)據(jù)時(shí):

  1. 處理名稱模糊查詢:生成名稱的通配符模式,比如 "手機(jī)",然后使用 SCAN 命令在 product_name_index 中查找所有分?jǐn)?shù)包含 "手機(jī)" 的成員(這里需要注意,SCAN 命令本身不能直接根據(jù)分?jǐn)?shù)的內(nèi)容進(jìn)行模糊查詢,所以前面的索引建立方式可能需要調(diào)整,更合理的做法是將商品名稱的關(guān)鍵詞提取出來(lái),作為有序集合的成員,分?jǐn)?shù)作為商品 ID,或者使用其他數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)關(guān)鍵詞和商品 ID 的映射關(guān)系。這里為了方便演示,假設(shè)我們有一個(gè)鍵為 name:手機(jī) 的集合,里面存儲(chǔ)了所有名稱包含 "手機(jī)" 的商品 ID)。
  2. 獲取價(jià)格在 2000 到 4000 之間的商品 ID 集合,使用 ZRANGEBYSCORE product_price_index 2000 4000。
  3. 對(duì)這兩個(gè)集合取交集,得到同時(shí)滿足名稱和價(jià)格條件的商品 ID 集合,可以使用 Redis 的 ZINTERSTORE 命令,將兩個(gè)有序集合的交集存儲(chǔ)到一個(gè)臨時(shí)有序集合中。
  4. 對(duì)臨時(shí)有序集合進(jìn)行分頁(yè)查詢,假設(shè)我們要按價(jià)格排序(也可以按其他條件排序),使用 ZRANGE 命令,根據(jù)頁(yè)碼和每頁(yè)數(shù)量計(jì)算出起始和結(jié)束索引,比如第 2 頁(yè),每頁(yè) 10 條,起始索引是 10,結(jié)束索引是 19,執(zhí)行 ZRANGE temp_index 10 19,得到該頁(yè)的商品 ID。
  5. 根據(jù)商品 ID 從對(duì)應(yīng)的字符串鍵中獲取商品的詳細(xì)信息,返回給用戶。

(三)代碼示例(Java 版本)

這里使用 Jedis 客戶端來(lái)演示部分代碼:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.*;
public class RedisQueryDemo {
    private Jedis jedis;
    public RedisQueryDemo() {
        jedis = new Jedis("localhost", 6379);
    }
    // 寫入商品數(shù)據(jù)并建立索引
    public void addProduct(String productId, String name, double price, String category) {
        // 存儲(chǔ)商品詳情
        String productKey = "product:" + productId;
        String productInfo = String.format("{\"name\":\"%s\", \"price\":%f, \"category\":\"%s\"}", name, price, category);
        jedis.set(productKey, productInfo);
        // 建立名稱索引(這里簡(jiǎn)化處理,實(shí)際可能需要更復(fù)雜的關(guān)鍵詞提?。?        jedis.zadd("product_name_index", 0, name + ":" + productId); // 這里分?jǐn)?shù)設(shè)為 0,僅作為存儲(chǔ)成員的方式,實(shí)際可根據(jù)需求設(shè)置
        // 建立價(jià)格索引
        jedis.zadd("product_price_index", price, productId);
        // 建立類別索引
        jedis.zadd("product_category_index", 0, category + ":" + productId); // 同理,分?jǐn)?shù)設(shè)為 0
    }
    // 多條件模糊查詢并分頁(yè)
    public List<String> searchProducts(String nameKeyword, double minPrice, double maxPrice, int page, int pageSize) {
        List<String> resultProductIds = new ArrayList<>();
        // 獲取名稱包含關(guān)鍵詞的商品 ID 集合(簡(jiǎn)化處理,實(shí)際需根據(jù)關(guān)鍵詞生成通配符并掃描)
        Set<String> nameMatchedProducts = new HashSet<>();
        // 這里模擬通過關(guān)鍵詞獲取相關(guān)成員,實(shí)際可能需要使用 SCAN 命令遍歷 product_name_index 并檢查成員是否包含關(guān)鍵詞
        Set<Tuple> nameIndexTuples = jedis.zrangeWithScores("product_name_index", 0, -1);
        for (Tuple tuple : nameIndexTuples) {
            String member = tuple.getElement();
            if (member.contains(nameKeyword)) {
                String productId = member.split(":")[1];
                nameMatchedProducts.add(productId);
            }
        }
        // 獲取價(jià)格范圍內(nèi)的商品 ID 集合
        Set<String> priceMatchedProducts = jedis.zrangeByScore("product_price_index", minPrice, maxPrice);
        // 取交集
        priceMatchedProducts.retainAll(nameMatchedProducts);
        // 將交集轉(zhuǎn)換為有序集合(假設(shè)按價(jià)格排序)
        String tempIndexKey = "temp_index:" + UUID.randomUUID().toString();
        int score = 0;
        for (String productId : priceMatchedProducts) {
            jedis.zadd(tempIndexKey, jedis.zscore("product_price_index", productId), productId);
        }
        // 分頁(yè)查詢
        long start = (page - 1) * pageSize;
        long end = start + pageSize - 1;
        resultProductIds = jedis.zrange(tempIndexKey, start, end);
        // 刪除臨時(shí)索引
        jedis.del(tempIndexKey);
        return resultProductIds;
    }
    public static void main(String[] args) {
        RedisQueryDemo demo = new RedisQueryDemo();
        // 模擬寫入數(shù)據(jù)
        demo.addProduct("1001", "華為手機(jī)", 3000, "電子產(chǎn)品");
        demo.addProduct("1002", "小米手機(jī)", 2500, "電子產(chǎn)品");
        demo.addProduct("1003", "蘋果手機(jī)", 4000, "電子產(chǎn)品");
        demo.addProduct("1004", "華為平板", 2000, "電子產(chǎn)品");
        demo.addProduct("1005", "海爾冰箱", 3500, "家電");
        // 模擬查詢:名稱包含"手機(jī)",價(jià)格在 2000 - 4000 之間,第 1 頁(yè),每頁(yè) 2 條
        List<String> productIds = demo.searchProducts("手機(jī)", 2000, 4000, 1, 2);
        for (String productId : productIds) {
            System.out.println("查詢到的商品 ID:" + productId);
            // 這里可以根據(jù) productId 獲取具體的商品信息
        }
    }
}

五、注意事項(xiàng)和優(yōu)化技巧

(一)索引維護(hù)

建立的索引會(huì)增加 Redis 的內(nèi)存占用,所以要根據(jù)實(shí)際的查詢需求,合理選擇需要建立索引的條件,不要建立過多無(wú)用的索引。同時(shí),在數(shù)據(jù)更新(比如刪除、修改)時(shí),要及時(shí)更新對(duì)應(yīng)的索引,保證索引的一致性。

(二)性能優(yōu)化

  1. 對(duì)于大規(guī)模數(shù)據(jù),使用 SCAN 命令代替 KEYS 命令進(jìn)行鍵的掃描,避免全量掃描影響 Redis 性能。
  2. 在進(jìn)行集合交集、并集等操作時(shí),注意集合的大小,如果集合過大,操作可能會(huì)比較耗時(shí),可以考慮在應(yīng)用層進(jìn)行部分過濾,減少 Redis 層的操作壓力。
  3. 可以對(duì)常用的查詢結(jié)果進(jìn)行緩存,比如熱門的查詢條件和分頁(yè)結(jié)果,減少重復(fù)查詢的開銷。

(三)數(shù)據(jù)結(jié)構(gòu)選擇

根據(jù)不同的業(yè)務(wù)場(chǎng)景選擇合適的數(shù)據(jù)結(jié)構(gòu),比如有序集合適合需要排序和范圍查詢的場(chǎng)景,集合適合需要去重和交集、并集操作的場(chǎng)景,字符串適合存儲(chǔ)單個(gè)對(duì)象的詳細(xì)信息。

六、總結(jié)

通過上面的方案,咱們基本上解決了 Redis 分頁(yè)和多條件模糊查詢的難題。利用有序集合、集合等數(shù)據(jù)結(jié)構(gòu)建立索引,對(duì)數(shù)據(jù)進(jìn)行預(yù)處理,結(jié)合分頁(yè)算法,能夠在 Redis 中實(shí)現(xiàn)高效的分頁(yè)和多條件查詢。當(dāng)然,具體的實(shí)現(xiàn)還需要根據(jù)項(xiàng)目的實(shí)際需求進(jìn)行調(diào)整和優(yōu)化,比如索引的建立方式、數(shù)據(jù)結(jié)構(gòu)的選擇、查詢條件的處理等。

責(zé)任編輯:武曉燕 來(lái)源: 石杉的架構(gòu)筆記
相關(guān)推薦

2023-11-17 15:34:03

Redis數(shù)據(jù)庫(kù)

2025-02-06 10:00:52

RedisSpring高性能

2022-06-20 15:19:51

前端監(jiān)控方案

2010-05-06 14:11:55

Oracle多條件查詢

2010-04-30 09:34:24

Oracle多條件查詢

2021-04-17 07:40:01

N卡驅(qū)動(dòng)應(yīng)用NVCleanstal

2018-06-11 17:27:56

APP流量華為

2010-04-28 16:45:27

Oracle Inst

2009-09-15 09:33:46

linq多條件查詢

2023-07-12 08:01:28

FOADMROADMOXC

2009-09-15 11:34:47

Linq多條件查詢

2009-06-29 09:03:31

Hibernate多條

2010-11-09 15:18:37

SQL Server多

2021-03-25 15:32:21

深度學(xué)習(xí)編程人工智能

2024-12-16 07:10:00

DockerDrone開發(fā)

2021-12-23 17:04:26

戴爾

2010-09-25 16:42:45

sql語(yǔ)句

2025-04-11 09:30:42

2022-09-14 08:11:06

分頁(yè)模糊查詢

2022-09-16 08:04:25

阿里云權(quán)限網(wǎng)絡(luò)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)