[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-mysqlsuijichaxunxingnengyouhuarengdiaoorder-by-randdezuijiashijian":3},{"id":4,"title":5,"slug":6,"summary":7,"content":8,"contentHtml":9,"wordCount":10,"readingTime":11,"categoryId":12,"tags":13,"coverImage":13,"thumbnail":13,"status":14,"isTop":15,"isRecommended":15,"allowComments":15,"password":13,"viewCount":16,"likeCount":15,"commentCount":15,"seoTitle":5,"seoKeywords":17,"seoDescription":18,"source":13,"sourceUrl":13,"publishTime":19},"2003004849359220738","MySQL随机查询性能优化：扔掉ORDER BY RAND()的最佳实践","mysqlsuijichaxunxingnengyouhuarengdiaoorder-by-randdezuijiashijian","> 面对海量数据随机推荐需求，如何平衡性能与随机性成为关键挑战 ## 背景与需求分析 在电商平台开发中，我们经常需要实现\"随机推荐\"功能：从商品库中随机选取指定数量的商品展示给用户。假设商品表(product)有10000条数据，需要随机获取3个不重复的商品。 许多开发者第一反应是使用`ORDER BY RAND()`实现，但这种方法的性能代价极高，在处理大量数据时几乎不可用。 ## 为什么不推荐使用ORDER BY RAND()？ ```sql -- 常见但不推荐的方案 SELECT * FROM product ORDER BY RAND() LIMIT 3; ``` 这条SQL语句的问题在于： - **需要全表扫描**：MySQL必须读取所有行并为每行分配随机值 - **使用临时表**：需要创建临时表存储所有数据 - **文件排序**：需要对整个临时表进行排序 - **性能随数据量增...","\n> 面对海量数据随机推荐需求，如何平衡性能与随机性成为关键挑战\n\n## 背景与需求分析\n\n在电商平台开发中，我们经常需要实现\"随机推荐\"功能：从商品库中随机选取指定数量的商品展示给用户。假设商品表(product)有10000条数据，需要随机获取3个不重复的商品。\n\n许多开发者第一反应是使用`ORDER BY RAND()`实现，但这种方法的性能代价极高，在处理大量数据时几乎不可用。\n\n## 为什么不推荐使用ORDER BY RAND()？\n\n```sql\n-- 常见但不推荐的方案\nSELECT * FROM product ORDER BY RAND() LIMIT 3;\n```\n\n这条SQL语句的问题在于：\n\n- **需要全表扫描**：MySQL必须读取所有行并为每行分配随机值\n- **使用临时表**：需要创建临时表存储所有数据\n- **文件排序**：需要对整个临时表进行排序\n- **性能随数据量增长急剧下降**：万级数据尚可勉强接受，十万级以上几乎不可用\n\n执行计划中会出现\"Using temporary\"和\"Using filesort\"，这些都是性能杀手。\n\n## 高性能替代方案实现与测试\n\n### 方案一：应用层随机（推荐首选）\n\n#### 实现思路\n1. 获取所有商品ID\n2. 在应用层进行随机洗牌\n3. 取前3个ID回表查询完整信息\n\n#### 具体实现\n\n```java\n\u002F\u002F 第一步：获取所有商品ID（只需执行一次并可缓存）\nList\u003CInteger> productIdList = productMapper.selectAllIds();\n\n\u002F\u002F 第二步：应用层随机处理\nCollections.shuffle(productIdList);\nList\u003CInteger> randomIds = productIdList.subList(0, 3);\n\n\u002F\u002F 第三步：回表查询完整信息\nList\u003CProduct> randomProducts = productMapper.selectByIds(randomIds);\n```\n\n对应的Mapper接口：\n```java\npublic interface ProductMapper {\n    \u002F\u002F 获取所有商品ID\n    @Select(\"SELECT id FROM product WHERE status = 1\")\n    List\u003CInteger> selectAllIds();\n    \n    \u002F\u002F 根据ID列表查询商品\n    @Select(\"\u003Cscript>\" +\n            \"SELECT id, name, price, image_url FROM product \" +\n            \"WHERE id IN \" +\n            \"\u003Cforeach collection='ids' item='id' open='(' separator=',' close=')'>\" +\n            \"#{id}\" +\n            \"\u003C\u002Fforeach>\" +\n            \"\u003C\u002Fscript>\")\n    List\u003CProduct> selectByIds(@Param(\"ids\") List\u003CInteger> ids);\n}\n```\n\n#### 性能测试结果\n| 数据量 | 平均响应时间 | QPS |\n|--------|--------------|-----|\n| 1万条  | 15ms | 66 |\n| 10万条 | 18ms | 55 |\n| 100万条| 25ms | 40 |\n\n### 方案二：使用LIMIT偏移量\n\n#### 实现思路\n1. 获取总数据量\n2. 计算随机偏移量\n3. 使用LIMIT获取数据\n\n#### 具体实现\n\n```java\n\u002F\u002F 获取商品总数（可缓存）\nint totalCount = productMapper.selectCount();\n\n\u002F\u002F 计算随机偏移量（确保不越界）\nRandom random = new Random();\nint offset = random.nextInt(Math.max(1, totalCount - 3));\n\n\u002F\u002F 执行分页查询\nList\u003CProduct> randomProducts = productMapper.selectWithOffset(offset, 3);\n```\n\n对应的Mapper接口：\n```java\npublic interface ProductMapper {\n    @Select(\"SELECT COUNT(*) FROM product WHERE status = 1\")\n    int selectCount();\n    \n    @Select(\"SELECT id, name, price, image_url FROM product \" +\n            \"WHERE status = 1 \" +\n            \"LIMIT #{offset}, #{limit}\")\n    List\u003CProduct> selectWithOffset(@Param(\"offset\") int offset, @Param(\"limit\") int limit);\n}\n```\n\n#### 性能测试结果\n| 数据量 | 平均响应时间 | QPS |\n|--------|--------------|-----|\n| 1万条  | 8ms | 125 |\n| 10万条 | 12ms | 83 |\n| 100万条| 45ms | 22 |\n\n### 方案三：多次查询取结果\n\n#### 实现思路\n1. 获取总数据量\n2. 生成多个随机偏移量\n3. 多次查询获取随机行\n\n#### 具体实现\n\n```java\npublic List\u003CProduct> getRandomProducts(int count) {\n    int totalCount = productMapper.selectCount();\n    Random random = new Random();\n    Set\u003CInteger> offsets = new HashSet\u003C>();\n    \n    \u002F\u002F 生成不重复的随机偏移量\n    while (offsets.size() \u003C count) {\n        int offset = random.nextInt(totalCount);\n        offsets.add(offset);\n    }\n    \n    \u002F\u002F 并行查询提升性能\n    return offsets.parallelStream()\n            .map(offset -> productMapper.selectWithOffset(offset, 1))\n            .flatMap(List::stream)\n            .collect(Collectors.toList());\n}\n```\n\n#### 性能测试结果\n| 数据量 | 平均响应时间 | QPS |\n|--------|--------------|-----|\n| 1万条  | 25ms | 40 |\n| 10万条 | 28ms | 35 |\n| 100万条| 35ms | 28 |\n\n### 方案对比总结\n\n| 方案 | 随机性 | 性能 | 实现复杂度 | 适用场景 |\n|------|--------|------|------------|----------|\n| ORDER BY RAND() | 优 | 差 | 简单 | 不推荐用于生产环境 |\n| 应用层随机 | 优 | 优 | 中等 | 数据量适中（推荐） |\n| LIMIT偏移量 | 中 | 良 | 简单 | 数据量大，随机性要求不高 |\n| 多次查询 | 良 | 中 | 复杂 | 数据量大，需要较好随机性 |\n\n## 性能优化进阶技巧\n\n### 1. 缓存优化策略\n\n```java\n@Service\npublic class ProductRandomService {\n    @Autowired\n    private RedisTemplate\u003CString, Object> redisTemplate;\n    \n    private static final String PRODUCT_IDS_KEY = \"product:all_ids\";\n    private static final String PRODUCT_COUNT_KEY = \"product:count\";\n    \n    \u002F\u002F 带缓存的随机查询\n    public List\u003CProduct> getRandomProductsWithCache(int count) {\n        \u002F\u002F 从缓存获取ID列表\n        List\u003CInteger> ids = getCachedProductIds();\n        if (ids.isEmpty()) {\n            ids = refreshProductIdsCache();\n        }\n        \n        Collections.shuffle(ids);\n        List\u003CInteger> randomIds = ids.subList(0, Math.min(count, ids.size()));\n        return productMapper.selectByIds(randomIds);\n    }\n    \n    \u002F\u002F 定时更新缓存\n    @Scheduled(fixedRate = 300000) \u002F\u002F 5分钟更新一次\n    public void refreshProductIdsCache() {\n        List\u003CInteger> ids = productMapper.selectAllIds();\n        redisTemplate.opsForValue().set(PRODUCT_IDS_KEY, ids, 10, TimeUnit.MINUTES);\n    }\n}\n```\n\n### 2. 加权随机推荐\n\n```java\n\u002F\u002F 基于热度的加权随机\npublic List\u003CProduct> getWeightedRandomProducts(int count) {\n    List\u003CProduct> hotProducts = productMapper.selectHotProducts(100); \u002F\u002F 取前100个热门商品\n    \n    \u002F\u002F 根据热度权重进行随机选择\n    double[] weights = hotProducts.stream()\n            .mapToDouble(p -> p.getHeatWeight())\n            .toArray();\n    \n    List\u003CProduct> result = new ArrayList\u003C>();\n    Random random = new Random();\n    \n    for (int i = 0; i \u003C count; i++) {\n        double randomValue = random.nextDouble() * Arrays.stream(weights).sum();\n        double cumulativeWeight = 0;\n        \n        for (int j = 0; j \u003C weights.length; j++) {\n            cumulativeWeight += weights[j];\n            if (randomValue \u003C= cumulativeWeight) {\n                result.add(hotProducts.get(j));\n                break;\n            }\n        }\n    }\n    \n    return result;\n}\n```\n\n## 实际应用建议\n\n1. **数据量小于10万**：推荐使用方案一（应用层随机），平衡性能与随机性\n2. **数据量大于10万**：可考虑方案二（LIMIT偏移量），但需要注意：\n   - 使用WHERE条件缩小范围后再随机\n   - 结合缓存减少数据库压力\n3. **超大数据量**：考虑使用专门的推荐系统\n   - 预先为每个用户生成推荐结果\n   - 使用Redis等缓存随机推荐结果\n4. **随机性要求极高**：可考虑组合方案\n   - 使用方案一获取随机ID\n   - 对极端情况（如重复推荐）做特殊处理\n\n## 测试环境配置\n\n- **数据库**：MySQL 8.0\n- **数据量**：1万\u002F10万\u002F100万条商品数据\n- **服务器配置**：4核8G内存\n- **测试工具**：JMeter 5.4.1\n- **并发用户数**：100\n- **测试时长**：5分钟\n\n## 总结\n\n通过实际测试对比，我们可以得出以下结论：\n\n1. **ORDER BY RAND()** 在生产环境中应避免使用，性能代价过高\n2. **应用层随机方案** 在大多数场景下表现最优，推荐作为首选方案\n3. **LIMIT偏移量方案** 适合数据量较大但对随机性要求不高的场景\n4. **合理的缓存策略** 可以进一步提升系统性能\n\n技术选型建议：对于大多数电商场景，方案一（应用层随机）是最佳选择，既能保证真正的随机性，又具有稳定的高性能表现。\n\n**记住：在数据库优化中，有时候将计算逻辑转移到应用层，反而能获得更好的整体性能。**","\u003Cblockquote data-line=\"1\">\n\u003Cp data-line=\"1\">面对海量数据随机推荐需求，如何平衡性能与随机性成为关键挑战\u003C\u002Fp>\n\u003C\u002Fblockquote>\n\u003Ch2 data-line=\"3\" id=\"背景与需求分析\">背景与需求分析\u003C\u002Fh2>\n\u003Cp data-line=\"5\">在电商平台开发中，我们经常需要实现&quot;随机推荐&quot;功能：从商品库中随机选取指定数量的商品展示给用户。假设商品表(product)有10000条数据，需要随机获取3个不重复的商品。\u003C\u002Fp>\n\u003Cp data-line=\"7\">许多开发者第一反应是使用\u003Ccode>ORDER BY RAND()\u003C\u002Fcode>实现，但这种方法的性能代价极高，在处理大量数据时几乎不可用。\u003C\u002Fp>\n\u003Ch2 data-line=\"9\" id=\"为什么不推荐使用ORDER BY RAND()？\">为什么不推荐使用ORDER BY RAND()？\u003C\u002Fh2>\n\n        \u003Cdetails  data-line=\"11\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">sql\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-sql\" language=sql>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">-- 常见但不推荐的方案\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">SELECT\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">*\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">FROM\u003C\u002Fspan> product \u003Cspan class=\"hljs-keyword\">ORDER\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">BY\u003C\u002Fspan> RAND() LIMIT \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>;\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"16\">这条SQL语句的问题在于：\u003C\u002Fp>\n\u003Cul data-line=\"18\">\n\u003Cli data-line=\"18\">\u003Cstrong>需要全表扫描\u003C\u002Fstrong>：MySQL必须读取所有行并为每行分配随机值\u003C\u002Fli>\n\u003Cli data-line=\"19\">\u003Cstrong>使用临时表\u003C\u002Fstrong>：需要创建临时表存储所有数据\u003C\u002Fli>\n\u003Cli data-line=\"20\">\u003Cstrong>文件排序\u003C\u002Fstrong>：需要对整个临时表进行排序\u003C\u002Fli>\n\u003Cli data-line=\"21\">\u003Cstrong>性能随数据量增长急剧下降\u003C\u002Fstrong>：万级数据尚可勉强接受，十万级以上几乎不可用\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp data-line=\"23\">执行计划中会出现&quot;Using temporary&quot;和&quot;Using filesort&quot;，这些都是性能杀手。\u003C\u002Fp>\n\u003Ch2 data-line=\"25\" id=\"高性能替代方案实现与测试\">高性能替代方案实现与测试\u003C\u002Fh2>\n\u003Ch3 data-line=\"27\" id=\"方案一：应用层随机（推荐首选）\">方案一：应用层随机（推荐首选）\u003C\u002Fh3>\n\u003Ch4 data-line=\"29\" id=\"实现思路\">实现思路\u003C\u002Fh4>\n\u003Col data-line=\"30\">\n\u003Cli data-line=\"30\">获取所有商品ID\u003C\u002Fli>\n\u003Cli data-line=\"31\">在应用层进行随机洗牌\u003C\u002Fli>\n\u003Cli data-line=\"32\">取前3个ID回表查询完整信息\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch4 data-line=\"34\" id=\"具体实现\">具体实现\u003C\u002Fh4>\n\n        \u003Cdetails  data-line=\"36\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 第一步：获取所有商品ID（只需执行一次并可缓存）\u003C\u002Fspan>\nList&lt;Integer&gt; productIdList = productMapper.selectAllIds();\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 第二步：应用层随机处理\u003C\u002Fspan>\nCollections.shuffle(productIdList);\nList&lt;Integer&gt; randomIds = productIdList.subList(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>);\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 第三步：回表查询完整信息\u003C\u002Fspan>\nList&lt;Product&gt; randomProducts = productMapper.selectByIds(randomIds);\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"48\">对应的Mapper接口：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"49\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ProductMapper\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 获取所有商品ID\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Select(&quot;SELECT id FROM product WHERE status = 1&quot;)\u003C\u002Fspan>\n    List&lt;Integer&gt; \u003Cspan class=\"hljs-title function_\">selectAllIds\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan>;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 根据ID列表查询商品\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Select(&quot;&lt;script&gt;&quot; +\n            &quot;SELECT id, name, price, image_url FROM product &quot; +\n            &quot;WHERE id IN &quot; +\n            &quot;&lt;foreach collection=&#x27;ids&#x27; item=&#x27;id&#x27; open=&#x27;(&#x27; separator=&#x27;,&#x27; close=&#x27;)&#x27;&gt;&quot; +\n            &quot;#{id}&quot; +\n            &quot;&lt;\u002Fforeach&gt;&quot; +\n            &quot;&lt;\u002Fscript&gt;&quot;)\u003C\u002Fspan>\n    List&lt;Product&gt; \u003Cspan class=\"hljs-title function_\">selectByIds\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@Param(&quot;ids&quot;)\u003C\u002Fspan> List&lt;Integer&gt; ids)\u003C\u002Fspan>;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch4 data-line=\"67\" id=\"性能测试结果\">性能测试结果\u003C\u002Fh4>\n\u003Ctable data-line=\"68\">\n\u003Cthead data-line=\"68\">\n\u003Ctr data-line=\"68\">\n\u003Cth>数据量\u003C\u002Fth>\n\u003Cth>平均响应时间\u003C\u002Fth>\n\u003Cth>QPS\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody data-line=\"70\">\n\u003Ctr data-line=\"70\">\n\u003Ctd>1万条\u003C\u002Ftd>\n\u003Ctd>15ms\u003C\u002Ftd>\n\u003Ctd>66\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"71\">\n\u003Ctd>10万条\u003C\u002Ftd>\n\u003Ctd>18ms\u003C\u002Ftd>\n\u003Ctd>55\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"72\">\n\u003Ctd>100万条\u003C\u002Ftd>\n\u003Ctd>25ms\u003C\u002Ftd>\n\u003Ctd>40\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 data-line=\"74\" id=\"方案二：使用LIMIT偏移量\">方案二：使用LIMIT偏移量\u003C\u002Fh3>\n\u003Ch4 data-line=\"76\" id=\"实现思路\">实现思路\u003C\u002Fh4>\n\u003Col data-line=\"77\">\n\u003Cli data-line=\"77\">获取总数据量\u003C\u002Fli>\n\u003Cli data-line=\"78\">计算随机偏移量\u003C\u002Fli>\n\u003Cli data-line=\"79\">使用LIMIT获取数据\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch4 data-line=\"81\" id=\"具体实现\">具体实现\u003C\u002Fh4>\n\n        \u003Cdetails  data-line=\"83\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 获取商品总数（可缓存）\u003C\u002Fspan>\n\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">totalCount\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> productMapper.selectCount();\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 计算随机偏移量（确保不越界）\u003C\u002Fspan>\n\u003Cspan class=\"hljs-type\">Random\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">random\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Random\u003C\u002Fspan>();\n\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">offset\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> random.nextInt(Math.max(\u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>, totalCount - \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>));\n\n\u003Cspan class=\"hljs-comment\">\u002F\u002F 执行分页查询\u003C\u002Fspan>\nList&lt;Product&gt; randomProducts = productMapper.selectWithOffset(offset, \u003Cspan class=\"hljs-number\">3\u003C\u002Fspan>);\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Cp data-line=\"95\">对应的Mapper接口：\u003C\u002Fp>\n\n        \u003Cdetails  data-line=\"96\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">interface\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ProductMapper\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-meta\">@Select(&quot;SELECT COUNT(*) FROM product WHERE status = 1&quot;)\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">selectCount\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan>;\n    \n    \u003Cspan class=\"hljs-meta\">@Select(&quot;SELECT id, name, price, image_url FROM product &quot; +\n            &quot;WHERE status = 1 &quot; +\n            &quot;LIMIT #{offset}, #{limit}&quot;)\u003C\u002Fspan>\n    List&lt;Product&gt; \u003Cspan class=\"hljs-title function_\">selectWithOffset\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-meta\">@Param(&quot;offset&quot;)\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> offset, \u003Cspan class=\"hljs-meta\">@Param(&quot;limit&quot;)\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> limit)\u003C\u002Fspan>;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch4 data-line=\"108\" id=\"性能测试结果\">性能测试结果\u003C\u002Fh4>\n\u003Ctable data-line=\"109\">\n\u003Cthead data-line=\"109\">\n\u003Ctr data-line=\"109\">\n\u003Cth>数据量\u003C\u002Fth>\n\u003Cth>平均响应时间\u003C\u002Fth>\n\u003Cth>QPS\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody data-line=\"111\">\n\u003Ctr data-line=\"111\">\n\u003Ctd>1万条\u003C\u002Ftd>\n\u003Ctd>8ms\u003C\u002Ftd>\n\u003Ctd>125\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"112\">\n\u003Ctd>10万条\u003C\u002Ftd>\n\u003Ctd>12ms\u003C\u002Ftd>\n\u003Ctd>83\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"113\">\n\u003Ctd>100万条\u003C\u002Ftd>\n\u003Ctd>45ms\u003C\u002Ftd>\n\u003Ctd>22\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 data-line=\"115\" id=\"方案三：多次查询取结果\">方案三：多次查询取结果\u003C\u002Fh3>\n\u003Ch4 data-line=\"117\" id=\"实现思路\">实现思路\u003C\u002Fh4>\n\u003Col data-line=\"118\">\n\u003Cli data-line=\"118\">获取总数据量\u003C\u002Fli>\n\u003Cli data-line=\"119\">生成多个随机偏移量\u003C\u002Fli>\n\u003Cli data-line=\"120\">多次查询获取随机行\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch4 data-line=\"122\" id=\"具体实现\">具体实现\u003C\u002Fh4>\n\n        \u003Cdetails  data-line=\"124\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> List&lt;Product&gt; \u003Cspan class=\"hljs-title function_\">getRandomProducts\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> count)\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">totalCount\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> productMapper.selectCount();\n    \u003Cspan class=\"hljs-type\">Random\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">random\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Random\u003C\u002Fspan>();\n    Set&lt;Integer&gt; offsets = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">HashSet\u003C\u002Fspan>&lt;&gt;();\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 生成不重复的随机偏移量\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">while\u003C\u002Fspan> (offsets.size() &lt; count) {\n        \u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">offset\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> random.nextInt(totalCount);\n        offsets.add(offset);\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 并行查询提升性能\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> offsets.parallelStream()\n            .map(offset -&gt; productMapper.selectWithOffset(offset, \u003Cspan class=\"hljs-number\">1\u003C\u002Fspan>))\n            .flatMap(List::stream)\n            .collect(Collectors.toList());\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch4 data-line=\"144\" id=\"性能测试结果\">性能测试结果\u003C\u002Fh4>\n\u003Ctable data-line=\"145\">\n\u003Cthead data-line=\"145\">\n\u003Ctr data-line=\"145\">\n\u003Cth>数据量\u003C\u002Fth>\n\u003Cth>平均响应时间\u003C\u002Fth>\n\u003Cth>QPS\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody data-line=\"147\">\n\u003Ctr data-line=\"147\">\n\u003Ctd>1万条\u003C\u002Ftd>\n\u003Ctd>25ms\u003C\u002Ftd>\n\u003Ctd>40\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"148\">\n\u003Ctd>10万条\u003C\u002Ftd>\n\u003Ctd>28ms\u003C\u002Ftd>\n\u003Ctd>35\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"149\">\n\u003Ctd>100万条\u003C\u002Ftd>\n\u003Ctd>35ms\u003C\u002Ftd>\n\u003Ctd>28\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch3 data-line=\"151\" id=\"方案对比总结\">方案对比总结\u003C\u002Fh3>\n\u003Ctable data-line=\"153\">\n\u003Cthead data-line=\"153\">\n\u003Ctr data-line=\"153\">\n\u003Cth>方案\u003C\u002Fth>\n\u003Cth>随机性\u003C\u002Fth>\n\u003Cth>性能\u003C\u002Fth>\n\u003Cth>实现复杂度\u003C\u002Fth>\n\u003Cth>适用场景\u003C\u002Fth>\n\u003C\u002Ftr>\n\u003C\u002Fthead>\n\u003Ctbody data-line=\"155\">\n\u003Ctr data-line=\"155\">\n\u003Ctd>ORDER BY RAND()\u003C\u002Ftd>\n\u003Ctd>优\u003C\u002Ftd>\n\u003Ctd>差\u003C\u002Ftd>\n\u003Ctd>简单\u003C\u002Ftd>\n\u003Ctd>不推荐用于生产环境\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"156\">\n\u003Ctd>应用层随机\u003C\u002Ftd>\n\u003Ctd>优\u003C\u002Ftd>\n\u003Ctd>优\u003C\u002Ftd>\n\u003Ctd>中等\u003C\u002Ftd>\n\u003Ctd>数据量适中（推荐）\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"157\">\n\u003Ctd>LIMIT偏移量\u003C\u002Ftd>\n\u003Ctd>中\u003C\u002Ftd>\n\u003Ctd>良\u003C\u002Ftd>\n\u003Ctd>简单\u003C\u002Ftd>\n\u003Ctd>数据量大，随机性要求不高\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003Ctr data-line=\"158\">\n\u003Ctd>多次查询\u003C\u002Ftd>\n\u003Ctd>良\u003C\u002Ftd>\n\u003Ctd>中\u003C\u002Ftd>\n\u003Ctd>复杂\u003C\u002Ftd>\n\u003Ctd>数据量大，需要较好随机性\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftbody>\n\u003C\u002Ftable>\n\u003Ch2 data-line=\"160\" id=\"性能优化进阶技巧\">性能优化进阶技巧\u003C\u002Fh2>\n\u003Ch3 data-line=\"162\" id=\"1. 缓存优化策略\">1. 缓存优化策略\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"164\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-meta\">@Service\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">class\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ProductRandomService\u003C\u002Fspan> {\n    \u003Cspan class=\"hljs-meta\">@Autowired\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> RedisTemplate&lt;String, Object&gt; redisTemplate;\n    \n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">final\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">PRODUCT_IDS_KEY\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;product:all_ids&quot;\u003C\u002Fspan>;\n    \u003Cspan class=\"hljs-keyword\">private\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">static\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">final\u003C\u002Fspan> \u003Cspan class=\"hljs-type\">String\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">PRODUCT_COUNT_KEY\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-string\">&quot;product:count&quot;\u003C\u002Fspan>;\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 带缓存的随机查询\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> List&lt;Product&gt; \u003Cspan class=\"hljs-title function_\">getRandomProductsWithCache\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> count)\u003C\u002Fspan> {\n        \u003Cspan class=\"hljs-comment\">\u002F\u002F 从缓存获取ID列表\u003C\u002Fspan>\n        List&lt;Integer&gt; ids = getCachedProductIds();\n        \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (ids.isEmpty()) {\n            ids = refreshProductIdsCache();\n        }\n        \n        Collections.shuffle(ids);\n        List&lt;Integer&gt; randomIds = ids.subList(\u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>, Math.min(count, ids.size()));\n        \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> productMapper.selectByIds(randomIds);\n    }\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 定时更新缓存\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-meta\">@Scheduled(fixedRate = 300000)\u003C\u002Fspan> \u003Cspan class=\"hljs-comment\">\u002F\u002F 5分钟更新一次\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">void\u003C\u002Fspan> \u003Cspan class=\"hljs-title function_\">refreshProductIdsCache\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">()\u003C\u002Fspan> {\n        List&lt;Integer&gt; ids = productMapper.selectAllIds();\n        redisTemplate.opsForValue().set(PRODUCT_IDS_KEY, ids, \u003Cspan class=\"hljs-number\">10\u003C\u002Fspan>, TimeUnit.MINUTES);\n    }\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch3 data-line=\"195\" id=\"2. 加权随机推荐\">2. 加权随机推荐\u003C\u002Fh3>\n\n        \u003Cdetails  data-line=\"197\" class=\"md-editor-code\" open=\"\">\n          \u003Csummary class=\"md-editor-code-head\">\n            \u003Cdiv class=\"md-editor-code-flag\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fdiv>\n            \u003Cdiv class=\"md-editor-code-action\">\n              \u003Cspan class=\"md-editor-code-lang\">java\u003C\u002Fspan>\n              \u003Cspan class=\"md-editor-copy-button\" data-tips=\"复制代码\">复制代码\u003C\u002Fspan>\n              \n              \u003Cspan class=\"md-editor-collapse-tips\">\u003Csvg xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"lucide lucide-circle-chevron-left md-editor-icon\">\u003Ccircle cx=\"12\" cy=\"12\" r=\"10\"\u002F>\u003Cpath d=\"m14 16-4-4 4-4\"\u002F>\u003C\u002Fsvg>\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fsummary>\n          \u003Cpre>\u003Ccode class=\"language-java\" language=java>\u003Cspan class=\"md-editor-code-block\">\u003Cspan class=\"hljs-comment\">\u002F\u002F 基于热度的加权随机\u003C\u002Fspan>\n\u003Cspan class=\"hljs-keyword\">public\u003C\u002Fspan> List&lt;Product&gt; \u003Cspan class=\"hljs-title function_\">getWeightedRandomProducts\u003C\u002Fspan>\u003Cspan class=\"hljs-params\">(\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> count)\u003C\u002Fspan> {\n    List&lt;Product&gt; hotProducts = productMapper.selectHotProducts(\u003Cspan class=\"hljs-number\">100\u003C\u002Fspan>); \u003Cspan class=\"hljs-comment\">\u002F\u002F 取前100个热门商品\u003C\u002Fspan>\n    \n    \u003Cspan class=\"hljs-comment\">\u002F\u002F 根据热度权重进行随机选择\u003C\u002Fspan>\n    \u003Cspan class=\"hljs-type\">double\u003C\u002Fspan>[] weights = hotProducts.stream()\n            .mapToDouble(p -&gt; p.getHeatWeight())\n            .toArray();\n    \n    List&lt;Product&gt; result = \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">ArrayList\u003C\u002Fspan>&lt;&gt;();\n    \u003Cspan class=\"hljs-type\">Random\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">random\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-keyword\">new\u003C\u002Fspan> \u003Cspan class=\"hljs-title class_\">Random\u003C\u002Fspan>();\n    \n    \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">i\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; i &lt; count; i++) {\n        \u003Cspan class=\"hljs-type\">double\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">randomValue\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> random.nextDouble() * Arrays.stream(weights).sum();\n        \u003Cspan class=\"hljs-type\">double\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">cumulativeWeight\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>;\n        \n        \u003Cspan class=\"hljs-keyword\">for\u003C\u002Fspan> (\u003Cspan class=\"hljs-type\">int\u003C\u002Fspan> \u003Cspan class=\"hljs-variable\">j\u003C\u002Fspan> \u003Cspan class=\"hljs-operator\">=\u003C\u002Fspan> \u003Cspan class=\"hljs-number\">0\u003C\u002Fspan>; j &lt; weights.length; j++) {\n            cumulativeWeight += weights[j];\n            \u003Cspan class=\"hljs-keyword\">if\u003C\u002Fspan> (randomValue &lt;= cumulativeWeight) {\n                result.add(hotProducts.get(j));\n                \u003Cspan class=\"hljs-keyword\">break\u003C\u002Fspan>;\n            }\n        }\n    }\n    \n    \u003Cspan class=\"hljs-keyword\">return\u003C\u002Fspan> result;\n}\u003C\u002Fspan>\u003Cspan rn-wrapper aria-hidden=\"true\">\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003Cspan>\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n\n        \u003C\u002Fdetails>\n      \u003Ch2 data-line=\"227\" id=\"实际应用建议\">实际应用建议\u003C\u002Fh2>\n\u003Col data-line=\"229\">\n\u003Cli data-line=\"229\">\u003Cstrong>数据量小于10万\u003C\u002Fstrong>：推荐使用方案一（应用层随机），平衡性能与随机性\u003C\u002Fli>\n\u003Cli data-line=\"230\">\u003Cstrong>数据量大于10万\u003C\u002Fstrong>：可考虑方案二（LIMIT偏移量），但需要注意：\n\u003Cul data-line=\"231\">\n\u003Cli data-line=\"231\">使用WHERE条件缩小范围后再随机\u003C\u002Fli>\n\u003Cli data-line=\"232\">结合缓存减少数据库压力\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli data-line=\"233\">\u003Cstrong>超大数据量\u003C\u002Fstrong>：考虑使用专门的推荐系统\n\u003Cul data-line=\"234\">\n\u003Cli data-line=\"234\">预先为每个用户生成推荐结果\u003C\u002Fli>\n\u003Cli data-line=\"235\">使用Redis等缓存随机推荐结果\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003Cli data-line=\"236\">\u003Cstrong>随机性要求极高\u003C\u002Fstrong>：可考虑组合方案\n\u003Cul data-line=\"237\">\n\u003Cli data-line=\"237\">使用方案一获取随机ID\u003C\u002Fli>\n\u003Cli data-line=\"238\">对极端情况（如重复推荐）做特殊处理\u003C\u002Fli>\n\u003C\u002Ful>\n\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 data-line=\"240\" id=\"测试环境配置\">测试环境配置\u003C\u002Fh2>\n\u003Cul data-line=\"242\">\n\u003Cli data-line=\"242\">\u003Cstrong>数据库\u003C\u002Fstrong>：MySQL 8.0\u003C\u002Fli>\n\u003Cli data-line=\"243\">\u003Cstrong>数据量\u003C\u002Fstrong>：1万\u002F10万\u002F100万条商品数据\u003C\u002Fli>\n\u003Cli data-line=\"244\">\u003Cstrong>服务器配置\u003C\u002Fstrong>：4核8G内存\u003C\u002Fli>\n\u003Cli data-line=\"245\">\u003Cstrong>测试工具\u003C\u002Fstrong>：JMeter 5.4.1\u003C\u002Fli>\n\u003Cli data-line=\"246\">\u003Cstrong>并发用户数\u003C\u002Fstrong>：100\u003C\u002Fli>\n\u003Cli data-line=\"247\">\u003Cstrong>测试时长\u003C\u002Fstrong>：5分钟\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 data-line=\"249\" id=\"总结\">总结\u003C\u002Fh2>\n\u003Cp data-line=\"251\">通过实际测试对比，我们可以得出以下结论：\u003C\u002Fp>\n\u003Col data-line=\"253\">\n\u003Cli data-line=\"253\">\u003Cstrong>ORDER BY RAND()\u003C\u002Fstrong> 在生产环境中应避免使用，性能代价过高\u003C\u002Fli>\n\u003Cli data-line=\"254\">\u003Cstrong>应用层随机方案\u003C\u002Fstrong> 在大多数场景下表现最优，推荐作为首选方案\u003C\u002Fli>\n\u003Cli data-line=\"255\">\u003Cstrong>LIMIT偏移量方案\u003C\u002Fstrong> 适合数据量较大但对随机性要求不高的场景\u003C\u002Fli>\n\u003Cli data-line=\"256\">\u003Cstrong>合理的缓存策略\u003C\u002Fstrong> 可以进一步提升系统性能\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp data-line=\"258\">技术选型建议：对于大多数电商场景，方案一（应用层随机）是最佳选择，既能保证真正的随机性，又具有稳定的高性能表现。\u003C\u002Fp>\n\u003Cp data-line=\"260\">\u003Cstrong>记住：在数据库优化中，有时候将计算逻辑转移到应用层，反而能获得更好的整体性能。\u003C\u002Fstrong>\u003C\u002Fp>\n",5471,18,"0","",2,0,14,"随机,数据,性能,&quot,推荐","\n面对海量数据随机推荐需求，如何平衡性能与随机性成为关键挑战\n\n背景与需求分析\n在电商平台开发中，我们经常需要实现&quot;随机推荐&quot;功能：从商品库中随机选取指定数量的商品展示给用户。假设商品表(product)有10000条数据，需要随机获取3个不重复的商品。\n许多开发者第一反应是使用ORDER BY RAND()实现，但这种方法的性能代价极高，在处理大量数据时几乎不可用。","2025-12-22 15:29:05"]