最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis排序后MySQL查詢亂序問題的原因及解決方法

 更新時(shí)間:2026年03月31日 08:27:45   作者:深藍(lán)軌跡  
在日常開發(fā)中,我們經(jīng)常會(huì)遇到需要按特定順序展示數(shù)據(jù)的場景,為了提升性能,很多開發(fā)者會(huì)用Redis做排序存儲(chǔ),再用MySQL查詢詳細(xì)數(shù)據(jù),這篇博客就詳細(xì)拆解這個(gè)高頻問題,從錯(cuò)誤場景、根本原因,到具體解決方案,需要的朋友可以參考下

引言

在日常開發(fā)中,我們經(jīng)常會(huì)遇到「需要按特定順序展示數(shù)據(jù)」的場景——比如按點(diǎn)贊時(shí)間展示前N名用戶、按操作時(shí)間展示最近操作記錄、按熱度排序展示內(nèi)容等。為了提升性能,很多開發(fā)者會(huì)用Redis做排序存儲(chǔ),再用MySQL查詢詳細(xì)數(shù)據(jù),但往往會(huì)遇到一個(gè)共性問題:Redis返回的順序是正確的,可MySQL查詢后,順序就徹底亂了。

這篇博客就詳細(xì)拆解這個(gè)高頻問題,從錯(cuò)誤場景、根本原因,到具體解決方案,再到核心代碼的逐行解析,全程通用,不管你做的是社交、電商還是其他項(xiàng)目,只要遇到「Redis排序+MySQL查詢」的組合,都能直接復(fù)用解決方案。

一、錯(cuò)誤出現(xiàn)的通用場景(不止某一個(gè)項(xiàng)目)

只要滿足以下3個(gè)條件,就大概率會(huì)遇到這個(gè)亂序問題,幾乎覆蓋所有需要「排序+詳情查詢」的業(yè)務(wù)場景:

  1. 用Redis的ZSet結(jié)構(gòu)存儲(chǔ)需要排序的數(shù)據(jù)(比如用戶ID、內(nèi)容ID),以時(shí)間戳、熱度值等作為score,實(shí)現(xiàn)按指定規(guī)則排序(如時(shí)間正序、熱度倒序);
  2. 需要從Redis中獲取排序后的前N條ID(比如前5個(gè)點(diǎn)贊用戶、前10條熱門內(nèi)容);
  3. 根據(jù)Redis返回的ID,去MySQL中查詢詳細(xì)數(shù)據(jù)(比如用戶頭像、昵稱,內(nèi)容標(biāo)題、作者等),最終將數(shù)據(jù)返回給前端展示。

最終表現(xiàn):前端展示的內(nèi)容順序,和Redis中排序的順序完全不一致,甚至毫無規(guī)律(比如按點(diǎn)贊時(shí)間排序,結(jié)果展示的是按用戶ID排序的頭像)。

二、完整執(zhí)行流程

我們用「按點(diǎn)贊時(shí)間展示前5個(gè)用戶頭像」這個(gè)最通用的場景,拆解從數(shù)據(jù)存儲(chǔ)到前端展示的全流程,清晰看到問題出在哪里。

步驟1:Redis存儲(chǔ)排序數(shù)據(jù)(順序完全正確)

為了實(shí)現(xiàn)「按點(diǎn)贊時(shí)間正序排序」,我們用Redis的ZSet存儲(chǔ)點(diǎn)贊記錄,核心邏輯如下(通用代碼,不限語言,這里以Java為例):

// key:業(yè)務(wù)標(biāo)識(shí)(如「內(nèi)容點(diǎn)贊集合_內(nèi)容ID」)
// value:需要排序的ID(如用戶ID)
// score:排序依據(jù)(如點(diǎn)贊時(shí)間戳,保證按時(shí)間正序排序)
stringRedisTemplate.opsForZSet().add("like:content:100", "103", System.currentTimeMillis());
stringRedisTemplate.opsForZSet().add("like:content:100", "101", System.currentTimeMillis() + 1000);
stringRedisTemplate.opsForZSet().add("like:content:100", "105", System.currentTimeMillis() + 2000);

Redis的ZSet會(huì)自動(dòng)根據(jù)score(時(shí)間戳)排序,最早點(diǎn)贊的用戶ID排在最前面。此時(shí)Redis中存儲(chǔ)的順序是:103 → 101 → 105(正確順序)。

步驟2:從Redis獲取排序后的ID(順序依然正確)

我們從Redis中獲取前5個(gè)點(diǎn)贊用戶的ID,代碼如下:

// range(0, 4):獲取排序后前5個(gè)ID,順序與Redis存儲(chǔ)一致
Set<String> sortedIds = stringRedisTemplate.opsForZSet().range("like:content:100", 0, 4);
// 轉(zhuǎn)換為Long類型集合,方便后續(xù)查詢MySQL
List<Long> ids = sortedIds.stream().map(Long::valueOf).collect(Collectors.toList());

此時(shí)ids集合的順序是:[103, 101, 105](依然是正確的點(diǎn)贊時(shí)間順序)。

步驟3:MySQL查詢詳細(xì)數(shù)據(jù)(順序被打亂)

我們需要根據(jù)上面的ids集合,去MySQL中查詢用戶的詳細(xì)信息(頭像、昵稱等),代碼如下:

// 根據(jù)ID集合查詢用戶,這是最常用的批量查詢方式
List<User> userList = userMapper.listByIds(ids);

這段代碼對(duì)應(yīng)的SQL語句(不管用什么ORM框架,最終都會(huì)生成類似SQL):

SELECT * FROM user WHERE id IN (103, 101, 105);

這里就是問題的核心:MySQL的IN查詢,不會(huì)按照我們傳入的ID順序返回結(jié)果!

MySQL默認(rèn)的排序規(guī)則是「按主鍵ID升序排列」,所以實(shí)際返回的userList順序是:101 → 103 → 105(打亂了Redis的正確順序)。

步驟4:直接返回前端(亂序展示)

如果我們不做任何處理,直接將MySQL查詢到的userList轉(zhuǎn)換為前端需要的格式并返回,前端就會(huì)按照「101 → 103 → 105」的順序展示頭像,和我們期望的「103 → 101 → 105」(點(diǎn)贊時(shí)間順序)完全不一致,問題爆發(fā)。

三、錯(cuò)誤的根本原因(通用,所有項(xiàng)目都適用)

很多開發(fā)者會(huì)誤以為是Redis排序出了問題,或者M(jìn)ySQL查詢出錯(cuò)了,但其實(shí)兩者都沒有錯(cuò),問題出在「兩者的職責(zé)差異」和「我們的遺漏處理」:

  1. Redis的職責(zé):只負(fù)責(zé)「存儲(chǔ)需要排序的ID」和「按指定規(guī)則排序」,不存儲(chǔ)詳細(xì)數(shù)據(jù)(如用戶頭像、昵稱),所以它只能返回排序后的ID,無法直接返回前端需要的完整數(shù)據(jù);
  2. MySQL的職責(zé):存儲(chǔ)詳細(xì)數(shù)據(jù),支持批量查詢,但MySQL的IN查詢「不保證返回順序」,默認(rèn)按主鍵ID升序排列(不同數(shù)據(jù)庫可能有差異,但都不會(huì)按傳入的IN參數(shù)順序返回);
  3. 我們的遺漏:沒有對(duì)MySQL返回的亂序數(shù)據(jù),做「順序修復(fù)」,直接將亂序數(shù)據(jù)返回給前端,導(dǎo)致展示錯(cuò)誤。

一句話總結(jié):Redis給了正確的順序,MySQL打亂了順序,我們沒修復(fù),所以亂序。

四、通用解決方案(核心,直接復(fù)制可用)

解決方案的核心思路非常簡單:保留Redis返回的正確ID順序,在Java內(nèi)存中,將MySQL查詢到的亂序數(shù)據(jù),按照正確的ID順序重新排序。

這種方式的優(yōu)勢:不操作數(shù)據(jù)庫,僅在內(nèi)存中排序,性能損耗可忽略不計(jì),且通用所有項(xiàng)目,不管你用的是Spring、MyBatis還是其他框架,都能直接復(fù)用。

完整修復(fù)代碼(通用Java版)

// 1. 從Redis獲取排序后的ID(正確順序)
String redisKey = "like:content:" + contentId; // 通用業(yè)務(wù)key,替換為自己的即可
Set<String> sortedIds = stringRedisTemplate.opsForZSet().range(redisKey, 0, 4);
// 處理空值,避免空指針
if (sortedIds == null || sortedIds.isEmpty()) {
    return Result.ok(Collections.emptyList()); // Result替換為自己項(xiàng)目的返回工具類
}
// 2. 轉(zhuǎn)換為Long類型的ID集合(正確順序)
List<Long> ids = sortedIds.stream().map(Long::valueOf).collect(Collectors.toList());
// 3. 從MySQL查詢用戶詳細(xì)數(shù)據(jù)(亂序)
List<User> userList = userMapper.listByIds(ids);
// 4. 關(guān)鍵:按照Redis的正確順序,重新排序用戶列表(核心修復(fù)代碼)
List<UserDTO> userDTOList = userList.stream()
        // 排序核心邏輯,下面會(huì)逐行詳解
        .sorted(Comparator.comparing(user -> ids.indexOf(user.getId())))
        // 轉(zhuǎn)換為前端需要的DTO(根據(jù)自己項(xiàng)目調(diào)整)
        .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
        .collect(Collectors.toList());
// 5. 返回給前端(此時(shí)順序已正確)
return Result.ok(userDTOList);

五、核心排序代碼逐行詳解(最易懂,小白也能懂)

很多開發(fā)者卡在這里,不是不會(huì)用,而是看不懂排序代碼的語法和作用,這里逐行拆解,全程大白話,不繞彎。

核心排序代碼(單獨(dú)拎出來,重點(diǎn)講解):

.sorted(Comparator.comparing(user -> ids.indexOf(user.getId())))

1. 先搞懂每個(gè)部分的作用(通俗版)

  • sorted():這是Java Stream流的排序方法,僅在內(nèi)存中排序,不操作任何數(shù)據(jù)庫,相當(dāng)于我們把MySQL查出來的亂序用戶列表,在代碼里手動(dòng)重新排了一遍;
  • Comparator.comparing():指定排序的「依據(jù)」——告訴程序,我們要按照什么規(guī)則來排序;
  • user -> ids.indexOf(user.getId()):排序的核心規(guī)則,我們拆成兩部分看:
    • user.getId():獲取當(dāng)前遍歷的用戶ID(比如101、103、105);
    • ids.indexOf(用戶ID):獲取這個(gè)用戶ID在「Redis正確順序的ids集合」中的「下標(biāo)位置」(下標(biāo)從0開始,數(shù)字越小,排越前)。

2. 用例子看懂執(zhí)行過程(最直觀)

已知:

  • Redis正確順序的ids集合:[103, 101, 105];
  • MySQL查詢返回的亂序userList:[101, 103, 105]

我們逐一遍歷userList中的每個(gè)用戶,計(jì)算排序依據(jù),再排序:

  1. 用戶101:ids.indexOf(101) → 下標(biāo)是1;
  2. 用戶103:ids.indexOf(103) → 下標(biāo)是0;
  3. 用戶105:ids.indexOf(105) → 下標(biāo)是2。

排序規(guī)則:按照「下標(biāo)數(shù)字從小到大」排序,所以最終排序后的順序是:

下標(biāo)0(103)→ 下標(biāo)1(101)→ 下標(biāo)2(105),和Redis的正確順序完全一致!

3. 一句話總結(jié)這段代碼的作用

「讓MySQL查出來的亂序用戶,按照Redis給出的正確順序,重新排隊(duì),還原我們想要的排序規(guī)則(比如點(diǎn)贊時(shí)間順序)」。

六、拓展方案:在MyBatis中直接排序(無需Java內(nèi)存排序)

如果你不想用Java代碼排序,也可以在MySQL層面直接強(qiáng)制排序,讓MySQL返回正確順序的結(jié)果,這種方式適合對(duì)SQL熟悉的開發(fā)者,同樣通用。

1. Mapper接口(通用版)

/**
 * 根據(jù)ID集合查詢用戶,按傳入的ID順序返回
 * @param ids Redis返回的正確順序ID集合
 * @return 按正確順序排列的用戶列表
 */
List<User> listByIdsWithOrder(@Param("ids") List<Long> ids);

2. MyBatis XML映射文件(核心SQL)

<select id="listByIdsWithOrder" resultType="com.xxx.entity.User">
    SELECT * FROM user
    WHERE id IN
    <foreach collection="ids" item="id" open="(" separator="," close=")">;
        #{id}
   </foreach>;
    <!-- 關(guān)鍵:強(qiáng)制按照傳入的ID順序排序 -->
    ORDER BY FIELD(id,
        <foreach collection="ids" item="id" separator=",">
            #{id}
        </foreach>
    )
</select>

3. 核心說明

ORDER BY FIELD(id, 103, 101, 105)是MySQL的專用語法,作用是「強(qiáng)制按照括號(hào)內(nèi)的ID順序返回結(jié)果」,括號(hào)內(nèi)的ID順序就是我們從Redis獲取的正確順序。

優(yōu)點(diǎn):查詢結(jié)果直接有序,無需Java代碼額外處理;缺點(diǎn):SQL復(fù)雜度略有提升,且僅適用于MySQL數(shù)據(jù)庫。

七、總結(jié)(通用,所有開發(fā)者必看)

1. 問題共性

只要用「Redis ZSet排序 + MySQL IN查詢詳細(xì)數(shù)據(jù)」,就一定會(huì)遇到「順序亂掉」的問題,這不是Redis或MySQL的bug,而是兩者的職責(zé)差異導(dǎo)致的。

2. 核心解決方案(優(yōu)先推薦)

用Java Stream的sorted(Comparator.comparing(user -> ids.indexOf(user.getId()))) ,在內(nèi)存中修復(fù)順序,通用、簡單、無性能損耗,直接復(fù)制可用。

3. 關(guān)鍵提醒

  • 不要誤以為MySQL的IN查詢會(huì)按傳入順序返回,這是很多開發(fā)者的常見誤區(qū);
  • 排序代碼不操作數(shù)據(jù)庫,僅內(nèi)存排序,不用擔(dān)心性能問題;
  • 不管你做的是點(diǎn)贊、熱門內(nèi)容、操作記錄等場景,只要涉及「Redis排序+MySQL查詢」,這個(gè)解決方案都能直接復(fù)用。

最后,希望這篇博客能幫到所有遇到同類問題的開發(fā)者,避免踩坑,高效解決亂序問題。

以上就是Redis排序后MySQL查詢亂序問題的原因及解決方法的詳細(xì)內(nèi)容,更多關(guān)于Redis排序后MySQL查詢亂序的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 基于Redis分布式鎖的實(shí)現(xiàn)代碼

    基于Redis分布式鎖的實(shí)現(xiàn)代碼

    這篇文章主要介紹了Redis分布式鎖的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • Redis延遲雙刪的具體使用

    Redis延遲雙刪的具體使用

    本文主要討論了延時(shí)雙刪策略,用于解決緩存與數(shù)據(jù)庫數(shù)據(jù)不一致的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-08-08
  • Redis序列化存儲(chǔ)及日期格式的問題處理

    Redis序列化存儲(chǔ)及日期格式的問題處理

    這篇文章主要介紹了Redis序列化存儲(chǔ)及其日期格式的問題處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Python連接Redis連接池的具體使用

    Python連接Redis連接池的具體使用

    在Redis中,同步和異步場景分別使用redis.ConnectionPool和redis.asyncio.ConnectionPool,配置選項(xiàng)包括最大連接數(shù)、超時(shí)時(shí)間等,多客戶端和單例模式也是連接池的常見應(yīng)用,下面就來詳細(xì)的介紹一下
    2026-03-03
  • 如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性

    如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性

    這篇文章主要介紹了如何保證Redis與數(shù)據(jù)庫的數(shù)據(jù)一致性,文中舉了兩個(gè)場景例子介紹的非常詳細(xì),需要的朋友可以參考下
    2023-05-05
  • Redis熱點(diǎn)Key問題的完整解決方案:

    Redis熱點(diǎn)Key問題的完整解決方案:

    熱點(diǎn)Key就像雙十一爆款商品,1%的Key承擔(dān)了90%的流量!本文將用電商案例,教你如何智能分流、化解壓力,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2025-08-08
  • Redis可視化連接服務(wù)器的方法

    Redis可視化連接服務(wù)器的方法

    這篇文章主要介紹了Redis可視化連接服務(wù)器的方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • Redis 跳表(Skip List)原理實(shí)現(xiàn)

    Redis 跳表(Skip List)原理實(shí)現(xiàn)

    跳表是zset有序集合的底層實(shí)現(xiàn)之一,本文主要介紹了Redis 跳表(Skip List)原理實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • redis集群搭建過程(非常詳細(xì),適合新手)

    redis集群搭建過程(非常詳細(xì),適合新手)

    這篇文章主要介紹了redis集群搭建過程,Redis集群至少需要3個(gè)節(jié)點(diǎn),因?yàn)橥镀比蒎e(cuò)機(jī)制要求超過半數(shù)節(jié)點(diǎn)認(rèn)為某個(gè)節(jié)點(diǎn)掛了該節(jié)點(diǎn)才是掛了,所以2個(gè)節(jié)點(diǎn)無法構(gòu)成集群,具體搭建過程跟隨小編一起看看吧
    2021-11-11
  • Redis之BigKey與HotKey問題詳解

    Redis之BigKey與HotKey問題詳解

    在Redis的實(shí)際使用過程中,BigKey和 HotKey是兩個(gè)常見且可能導(dǎo)致嚴(yán)重性能問題的問題,它們可能導(dǎo)致 Redis 實(shí)例響應(yīng)變慢、內(nèi)存使用不均、甚至服務(wù)不可用,下面就來介紹一下這兩個(gè)的問題
    2026-02-02

最新評(píng)論

佛坪县| 台东县| 北票市| 新民市| 清新县| 治多县| 临洮县| 麻江县| 南岸区| 麻城市| 贵南县| 库伦旗| 万荣县| 板桥市| 阿图什市| 澜沧| 大姚县| 长宁县| 博罗县| 焉耆| 夏河县| 土默特左旗| 且末县| 仪陇县| 凤城市| 南宁市| 临西县| 荔波县| 九江市| 徐州市| 雅江县| 唐山市| 舟山市| 竹山县| 习水县| 棋牌| 台州市| 沂南县| 新源县| 光泽县| 乡宁县|