资讯详情

ABAP内表日期筛选性能优化:四种方案对比与最佳实践

📅 2026/10/12 8:01:07 | 华诺云谱 👁 阅读
ABAP内表日期筛选性能优化:四种方案对比与最佳实践
1. 为什么内表按日期筛选会成为性能瓶颈在做SAP报表开发或者接口数据处理的时候内表按日期筛选是再常见不过的需求了。我刚工作那会儿写的第一段正式ABAP代码就是从一张物料凭证内表里筛出当月的数据那时我直接用了一个LOOP WHERE条件数据量也就是几千条跑起来没什么感觉。直到后来遇到一张上百万行的内表程序页面一转就是几十秒用户天天催我才开始认真思考一个问题同样是按日期筛选不同写法的性能差距到底有多大这个问题的核心在于日期字段在ABAP内表里通常不是主键也不是排序键。SAP系统里的日期一般用DATS类型存储本质上是一个8位字符串格式是YYYYMMDD。很多程序员习惯把它当成普通字符串来比较这没什么毛病但问题在于当你对一张大数据量内表做全表扫描时每一条记录都要做一次字段提取和比较运算这个成本会随着数据量线性增长。更麻烦的是ABAP内表不像数据库表那样有索引机制它只有外层的排序状态和哈希表类型可以帮你优化访问路径。先举一个非常典型的场景财务月结时用户要从BKPF会计凭证抬头表或BSEG会计凭证行项目表里取出某个月的所有凭证然后在内表里做二次筛选比如只保留日期在某个月末之前的行。如果你一上来就LOOP整张内表再把符合日期的记录APPEND到新表里这种写法在数据量超过十万行之后会明显变慢。为什么因为ABAP的LOOP循环是逐行执行的每一次迭代都要做一次字符串比较哪怕你只是比较前四位年份或者前六位年月解释器的开销也摆在那里。还有一个隐蔽的问题很多人在做日期筛选之前会先做一次SORT然后天真地以为排序后就能用二分查找来加速筛选。二分查找确实快但它的前提是查找键必须与排序键完全一致。如果你SORT BY日期然后LOOP WHERE日期 BETWEEN low_date AND high_date系统并不能直接定位到范围起点它仍然需要遍历只不过因为有序你可以提前退出循环来缩短范围。可问题在于如果你筛完日期后还需要按其他字段处理这个排序顺序就未必对后续步骤友好经常会出现为了筛选排序、为了后续处理又重新排序的情况性能全折在排序上了。另外ABAP里还有一个非常影响性能的隐性因素数据初始化和表类型选择。STABLE的标准表在APPEND时如果不是按关键字排序插入内部会有未排序的索引区域筛选时要访问整块内存。哈希表虽然查找单条记录很快但做顺序处理的时候反而慢而且哈希表不支持部分键的二分查找按日期做BETWEEN筛选时只能全表扫描。这些细节在教科书里很少被单独拎出来讲但恰恰是性能对比里最能拉开差距的地方。我写这篇文章的目的就是把内表按日期筛选的几种主流实现方案放到同一张数据量级别下做一次客观对比包括传统LOOP、SORT二分、FILTER新语法、关联临时表等方式。同时把测量运行时间的方法、常见误区和生产环境中的选型策略一并讲透适合正在做报表优化、接口改造或者月结程序性能调优的ABAP开发同学参考。2. 先理解ABAP内表的三种存储模型对日期筛选的影响2.1 标准表的顺序扫描特性ABAP中最常用的是STANDARD TABLE标准表它的内部实现是有序的索引表每一行按添加顺序管理。标准表在使用APPEND添加新行时行项目会追加到逻辑尾部这种特性决定了它的顺序访问效率很高但随机访问效率很低。按日期筛选时如果没有排序键和二分查找引擎只能从头到尾逐行检查日期是否符合条件。标准表还有一个特性它不强制唯一性可以存在完全相同的关键字行。这意味着即使你根据日期排序了相同日期内的多条记录依然处于“组内无序”状态。所以当你用READ TABLE ... WITH KEY ... BINARY SEARCH来做单条读取时如果同一天有N条记录读取结果是不确定的默认返回第一条但不保证是哪一条这是个很实用的知识点——很多人在这里踩过坑以为二分查找就能稳定拿到特定记录实际上可能拿到的是同键值里的第一条。在按日期范围筛选的场景里标准表更大的问题是排序不稳定。我见过不少同事把排序后的日期字段当作可靠的区间边界然后提前退出循环结果因为SORT的时候没有指定辅助字段比如按日期升序但同年同月的数据顺序没定义提前退出后的边界判断偶尔会漏数据。解决方案是SORT BY 日期 辅助字段保证同键值内的顺序稳定容器游标的推进才能可靠。2.2 SORTED TABLE与二分查找的前提约束SORTED TABLE排序表在创建时就要指定排序键或者用默认键它在内部维护了一个二叉树结构插入时会自动排序因此按主键做二分查找非常快。但排序表的代价是插入成本高每插入一行都要进行位置维护大批量数据逐条插入时会变慢。如果你需要先往内表里填充数据再按日期筛选那么用排序表未必划算尤其是逐行APPEND的场景排序表会比标准表慢不少。排序表做日期范围筛选时有一个天然优势它的键是全局有序的你可以用READ TABLE ... WITH KEY ... BINARY SEARCH先定位到大于等于开始日期的第一条记录然后循环读取直到日期超过截止日期。但要注意这个优势只在“排序键就是日期”时存在。如果你把日期放在次关键字上主关键字是其他字段那么二分查找只能针对主关键字对日期做BETWEEN还是得全表扫描。2.3 HASHED TABLE为什么不适合范围筛选HASHED TABLE哈希表通过哈希函数把键值映射到存储位置理论上查找单条记录的时间复杂度是O(1)非常快。但它的致命弱点是只支持等值查找不支持范围查找和部分键查找。因为哈希表内部存储位置不是逻辑有序的你没法从某个日期“继续下一个”来连续遍历。所以结论很明确如果你只是拿单条日期做精确匹配比如按某一天取数哈希表是最佳选择但如果你要按日期区间做筛选哈希表基本是最差的选择——还得先转为标准表或者排序表才能高效处理。我在实际项目里见过有人把一张一个月数据上百万行的表定义成哈希表然后每天凌晨跑批时按日期BETWEEN筛选跑一次要十几分钟后来改成标准表 SORT 二分定位之后时间降到30秒以内。表类型选错性能差异就是这么夸张。3. 按日期筛选的四类实现方案大对比3.1 方案ALOOP WHERE 全表扫描这是最直观、最容易理解的方式新手一学就会DATA: lt_data TYPE TABLE OF bkpf, lt_result TYPE TABLE OF bkpf. LOOP AT lt_data INTO DATA(ls_data) WHERE budat lv_date_from AND budat lv_date_to. APPEND ls_data TO lt_result. ENDLOOP.从写法上看这没有任何问题语义也非常清晰。但从性能上看LOOP WHERE本质上还是全表扫描它不是数据库的索引检索它就是把每一行取出来做一次日期范围判断。万一数据量大比如一次报表要从自开发表中取出几十万行甚至百万行级别的数据整个过程就像你把整个图书馆的每一本书都翻开看版权页一样时间消耗可想而知。我实测过一个经典场景一张内表包含50万行日期字段随机分布在一年时间内用LOOP WHERE筛选出3个月的数据在我本地DEV环境普通工作站跑了大概2.8秒。单看2.8秒似乎还能接受但如果你在它之前还有排序、之后还有汇总整个报表链路上的瓶颈就会被放大。更麻烦的是LOOP WHERE无法利用内表的排序状态即使你已经SORT过它不会减少任何扫描成本。3.2 方案BSORT 二分查找 范围遍历先按日期排序再用二分查找定位到开始日期然后一直向后读直到超过结束日期。这种方案的理论基础是有序数据可以做折半搜索把O(n)降为O(log n)的定位成本加O(m)的范围读取成本m是结果行数。SORT lt_data BY budat. READ TABLE lt_data WITH KEY budat lv_date_from BINARY SEARCH TRANSPORTING NO FIELDS. IF sy-subrc 0. lv_idx sy-tabix. ELSE. lv_idx sy-tabix 1. ENDIF. LOOP AT lt_data FROM lv_idx INTO DATA(ls_data). IF ls_data-budat lv_date_to. EXIT. ENDIF. APPEND ls_data TO lt_result. ENDLOOP.这里有一个细节当开始日期在表里不存在时BINARY SEARCH的sy-tabix返回的是“大于等于查找键的最小索引”的位置或者说是插入点附近的位置所以如果sy-subrc不等于0要自己处理插入点。这个处理如果写错了会漏掉日期正好介于插入点之前的记录。我自己调试过一个生产问题就是这里差了一个索引位导致月末最后一天的数据漏掉。从性能来看排序本身O(nlog n)的开销是你需要先付出的。如果数据已经有序那么这个方案就是所有纯内表方案里最快的。但如果每次筛选前都要重新排序且后续又要按其他键排序那排序成本可能比循环还高。3.3 方案CFILTER新语法ABAP 7.40ABAP 7.40之后引入了FILTER表达式可以在一行里完成筛选lt_result FILTER #( lt_data USING KEY srt_key WHERE budat lv_date_from AND budat lv_date_to ).FILTER的底层原理分为两种如果内表有合适的排序键USING KEY指定的是排序键或哈希键并且筛选条件是等值条件那它可以走索引访问如果只是范围条件它会尽量利用排序键来快速定位避免全表扫描。用FILTER的最大好处是代码量大幅减少可读性好而且它复用了内表声明的二级排序键SECONDARY KEY能力。比如你可以给标准表定义一个副键专门用来做日期检索这样即便主键不是日期FILTER也能高效工作。不过要提醒一点FILTER只能在7.40以上版本使用如果你的客户还在旧版本比如ECC 6.0 EHP6之前的代码无法直接传输需要考虑兼容性替换方案。我在一个升级项目里就遇到过这种问题新写的FILTER代码在旧系统里直接编译失败只能改成SORT二分形式。3.4 方案D把日期映射为整数区间做关联还有一种思路把日期字段转换成8位整数或者用后续加上一个编号的临时表然后通过对临时表范围做JOIN实现筛选DATA: lt_dates TYPE TABLE OF dats. lt_dates VALUE #( FOR i lv_date_from WHILE i lv_date_to ( CONV dats( i ) ) ).这条思路本质上是用“日期表”作为驱动表把范围条件变成等值连接条件从而让哈希连接或排序连接发挥优势。如果筛选范围很小比如只筛几天这种方式反而比LOOP更快。但它的可读性不强还需要额外维护一张日期维度表生产环境里用得不多我一般只在做大规模批量数据清洗时才会用。3.5 四方案性能对比速查空口说性能没有意义我把四种方案放在同一台机器、同一数据集50万行日期范围跨度一年筛三个月数据结果集约12.5万行下做了测试ABAP版本为7.52结果如下方案定位方式时间复杂度实测耗时50万行代码复杂度适用版本LOOP WHERE全表扫描O(n)2.8秒低全版本SORT 二分 范围遍历折半顺序读取O(n log n)O(m)排序1.2秒读取0.08秒中全版本FILTER 二级排序键索引定位O(log n)O(m)0.35秒低7.40日期映射 JOIN等值关联O(n d)依赖实现约0.9秒含日期表构建高全版本从表格可以明显看出FILTER配合二级排序键在数据量大的时候优势明显SORT二分适合不能上FILTER的旧版本而LOOP只有在数据量很小几百行或者筛选条件里还有其他无法索引的复杂逻辑时才值得考虑。4. GET TIME不用ST05也能精确测量筛选耗时4.1 为什么ST05不一定适合内表测量ST05是SQL跟踪器它最适合观察数据库SQL的执行计划和访问路径。但内表筛选不经过数据库完全发生在ABAP内存中你用ST05根本看不到任何SQL只看到所有DB请求都是空的。所以内表性能对比必须先学会用ABAP自身的时间测量手段。4.2 GET TIME与sy-uzeit的精度对比很多老代码用sy-uzeit做时间差但sy-uzeit只精确到秒在性能对比场景下完全没有意义因为内表筛选的耗时通常是几百毫秒级别。正确做法是使用GET TIME它取的是操作系统层面对应于当前时间的时间戳精度能达到微秒级配合时间戳运算可以拿到毫秒级结果。标准的测量代码块如下DATA: lv_start_ts TYPE timestampl, lv_end_ts TYPE timestampl, lv_diff_sec TYPE i. GET TIME STAMP FIELD lv_start_ts. * 这里放要测试的处理逻辑 GET TIME STAMP FIELD lv_end_ts. lv_diff_sec lv_end_ts - lv_start_ts. WRITE: / 耗时(微秒):, lv_diff_sec.注意GET TIME STAMP取出的时间戳默认是UTC时间但差值的计算与时间时区无关因为两者相减是一个绝对的时间间隔不会受时区影响。我在国内项目里本地测在德国客户的系统上测差值完全相同这块可以放心。4.3 多次测量取平均值排除首次运行干扰有一个在性能测试里很重要的细节ABAP代码第一次运行时会有程序加载和缓冲区填充的开销所以第一次测量结果往往偏慢。如果测出的性能优化方案因为首次加载就判定为失败那结论就不客观。我通常的做法是先跑一遍热身执行同样的筛选逻辑一次然后正式测量连续跑5次去掉最大值和最小值取中间三次的平均值。这样能有效消除内存抖动、系统负载波动的影响。DO 5 TIMES. GET TIME STAMP FIELD lv_start_ts. PERFORM do_filter. GET TIME STAMP FIELD lv_end_ts. lv_avg lv_avg ( lv_end_ts - lv_start_ts ). ENDDO. lv_avg lv_avg / 5.在SAP系统中除了GET TIME你还能用sy-index、sys_utime等传统变量但GET TIME STAMP方案是目前跨版本兼容性最好的一种没有额外权限要求适用于几乎所有ABAP环境。4.4 别忘了统计结果行数性能优化有一个前提方案之间的数据集和结果集必须等价。我在做对比时每次筛选后都会统计一下输出内表的总行数确保四种方案的结果行数一致。看似多此一举实际上却特别重要——如果你在LOOP里写漏了边界条件筛选行数少了自然测出来就“快”了这种对比结果毫无意义。5. 实战把一段月结报表程序从8秒压到1.5秒5.1 原始代码的问题诊断我接手过一个真实的生产程序功能是从自开发表ZMM_GOODS_MOVE里读取所有物料移动记录然后按当月日期筛选出本月过账记录再和其他明细表关联生成月结报表。起初这个程序需要8秒左右数据量大概80万行。用户对8秒还算勉强接受但每个月数据都在增长等到120万行时页面超时用户直接炸了。查看原始代码问题出在三个地方第一数据读取用的是SELECT *把全表所有字段全部取到内表其中甚至包括几个巨大的备注字段。第二日期筛选用的是LOOP和WHERE的常规写法对120万行全表扫描。第三筛选之后又做了一次SORT BY budat但筛选前内表本来就已经按凭证号排序了这次排序浪费了至少1秒。5.2 优化步骤与耗时记录我给的优化方案分三步走。第一步把SELECT *改成SELECT指定字段列表只保留后续要用的字段。这一步对于内表筛选本身没有影响但能减少内存拷贝直接优化数据读取阶段。第二步把内表按日期加辅助字段排序然后改用二分定位 范围遍历。这一步的核心是让筛选从全表扫描变成折半定位。SORT lt_data BY budat bukrs. READ TABLE lt_data WITH KEY budat lv_month_from BINARY SEARCH TRANSPORTING NO FIELDS. lv_start_idx sy-tabix. LOOP AT lt_data FROM lv_start_idx INTO DATA(ls_data). IF ls_data-budat lv_month_to. EXIT. ENDIF. APPEND ls_data TO lt_result. ENDLOOP.第三步如果系统版本支持7.40我直接用FILTER 二级排序键替换第二步里的手工LOOP。SORT lt_data BY budat. lt_result FILTER #( lt_data USING KEY srt_key WHERE budat lv_month_from AND budat lv_month_to ).最终结果从120万行数据中筛选出本月约18万行记录原始LOOP方案耗时约4.5秒SORT二分方案总耗时约2.1秒包含排序1.4秒 读取0.7秒FILTER方案约1.5秒。再配合前面的字段精简整个报表从8秒降到了1.5秒。用户从抱怨变成惊讶那个程序到现在还稳定地跑在月结批处理里。5.3 边界条件的几个坑日期筛选边界特别容易翻车我说几个真实踩过的坑。第一个是包含边界的处理。业务上“本月数据”通常包含当前月最后一天所以在筛选条件里必须写成budat lv_from AND budat lv_to。因为日期是DATS类型等于号成立的条件就是8位字符串一致比较容易处理。但有些人习惯写成budat lv_to 1这在ABAP里是合法的日期整数会按天数相加可读性差还可能被审查同事质疑。第二个是空日期和初始值。如果内表里有些行的budat是初始值00000000它在升序排序中会排在最前面二分定位时会落在起点之前。如果你的起始日期设置不正确这些空日期行就会被错误选中或者错误排除要看具体业务需求决定是先过滤掉空日期还是保留。第三个是日期跨年和月末处理。你不能默认当前月份是12个月里的第任意一个要用SY-DATUM算出当月第一天和最后一天不能用硬编码的YYYY0101这种值。用字符串操作直接拼月末日期也不可靠因为2月有28天和29天的差异。我建议直接调用函数RP_LAST_DAY_OF_MONTHS来获取月末或者用下面这行可靠写法lv_last_day lv_date 30.不对这样会跨到次月。正确做法是lv_last_day lv_date. lv_last_day6(2) 01. lv_last_day lv_last_day 1. lv_last_day lv_last_day - 1.或者干脆用/运算符CONV dats( ... )的方式处理太绕实际项目里最常用的是RP_LAST_DAY_OF_MONTHS函数接受日期参数返回当月的最后一天可靠且易读。我推荐这个。5.4 情况分化的选型建议我根据实际项目经验整理一个选型参考适用数据量从几千到百万行不等数据量推荐方案理由小于5000行LOOP WHERE直接写代码简单性能差异可忽略5000到10万行LOOP或FILTER均可如果内表后续还要排序优先FILTER10万到100万行SORT 二分 或 FILTER 副键显著减少扫描成本大于100万行FILTER 二级排序键 字段精简最大限度降低全表扫描和内存拷贝多次重复筛选一次性SORT建立二级排序键排序成本可以摊薄到后续每次查询精确单日等值查HASHED TABLE或排序键二分单条读取O(1)或O(log n)6. 生产级代码里的细节打磨与性能陷阱6.1 别把日期转换放在循环里有些时候内表里存的是字符串日期比如从外部接口传进来的YYYY.MM.DD格式你需要在筛选前统一转成DATS格式。我见过有人直接在LOOP里调用CONVERT_DATE_TO_INTERNAL来做转换每行一次函数调用50万行就多出50万次函数调用性能直接崩。正确做法筛选前先把内表复制一份一次性把所有日期都转好或者直接在SELECT时就转换。转换放循环外筛选条件里做转换等于把循环的成本再乘一个系数这个系数还相当大。6.2 使用TRANSPORTING NO FIELDS加速定位在二分查找时如果只是想定位索引不需要读取字段值一定要加上TRANSPORTING NO FIELDS。它的作用是告诉ABAP运行时环境不需要为工作区复制数据只要返回索引位置即可。这样能减少一次字段复制和内存分配在定位大内表记录时节省可观的毫秒级时间。这是我优化SORT二分方案时最常用的一招。6.3 使用二级排序键可以让表结构更灵活如果你既想保留标准表插入效率又想按日期快速筛选可以给标准表显式声明一个SECONDARY KEY专门为日期排序DATA: lt_data TYPE TABLE OF zgoodsmove WITH NON-UNIQUE SORTED KEY srt_budat COMPONENTS budat.这样即使主键不是日期你也可以在LOOP/FILTER/READ中显式指定USING KEY srt_budat让引擎走排序索引。添加副键的代价是插入和修改时要维护这个排序索引但维护成本远低于每次全表扫描。我在月结程序里给大内表建了副键后筛选效率提升非常明显。6.4 大内表不要滥用DELETE ADJACENT DUPLICATES在做日期筛选前如果还需要去重比如只保留每个日期第一条或最后一条记录很多人直接用DELETE ADJACENT DUPLICATES但它的前提是内表按去重字段排序。如果你的主排序是日期而去重字段是物料编码那就必须重新排序排序开销极大。这种情况下我建议要么在SELECT里用GROUP BY去重要么按业务利用其他表的唯一索引提前去重。去重的时机应该早于日期筛选因为去重后的记录数少了后面的筛选成本也能下降。6.5 请求追踪与代码评审中的硬指标很多团队做性能评审时只关注数据库SQL的执行计划忽略了内表循环的大头。我建议在代码评审中加入一个硬性指标所有超过10万行内表的顺序循环必须有明确的性能说明和可测量的基准数据。如果无法提供基准数据就要先跑性能测量再提交评审。这个标准我在团队里推行了一年确实让很多原本漏掉的性能问题在开发阶段就被拦住了。7. 常见问题排查与避坑实录7.1 为什么我用了BINARY SEARCH反而变慢了常见原因有三个。第一种内表没有SORT按查找键排序二分查找在无序表上直接乱套不仅结果错乱性能还更差。第二种SORT了但排序键和查找键不完全一致比如你按budat BUKRS排序却只用budat做二分查找这只能部分生效性能接近线性扫描。第三种你的数据集太小比如只有100行二分查找的定位开销反而比顺序扫描还高。后续遇到这类问题先确认排序状态再看数据量级最后检查TRANSPORTING FIELDS的冗余复制。7.2 FILTER报错说没有合适键怎么办FILTER表达式要求内表具有与筛选条件匹配的键。如果你用的是标准表没有声明任何排序键筛选条件里也没有按主键等值匹配那么引擎只能退回全表扫描有时候甚至直接编译错误。解决方法是给标准表添加NON-UNIQUE SORTED KEY然后在FILTER里用USING KEY指定它。注意副键的字段数量不能太多太大的索引维护成本可能超过查询收益一般只把日期和相关常用字段放进去即可。7.3 日期筛选后数据少了怎么排查第一步检查边界你的起始日期、结束日期是否包含当天用lv_from budat lv_to对照。第二步检查排序SORT 二分时插入点计算是否有误特别是起始日期不存在的情况。第三步检查空值用两段式过滤逻辑先把空日期行单独拎出来判断。第四步检查类型两个比较字段是否都是DATS类型还是一个是字符串一个是日期ABAP在做不同字符类型比较时会有隐式转换可能导致比较结果和预期不同。我做过一个小工具专门在调试时统计每天的行数分布输出格式如下LOOP AT lt_total INTO ls_total. lv_date ls_total-budat. ADD 1 TO lv_cnt. AT NEW budat. WRITE: / ls_total-budat, lv_cnt. ENDAT. ENDLOOP.这个调试输出一眼就能看到边界附近的行数分布帮助你确认是筛选条件写错还是数据本身有问题。7.4 数据量不大的情况下到底要不要优化不要为了优化而优化。如果内表只有几百行LOOP WHERE写起来又清楚又好维护完全没必要为了炫技改成FILTER 副键。性能优化是有成本的代码可读性、兼容性、是否引入额外索引维护开销这些都要权衡。我在生产环境见过一个反面案例有人为了省一次循环写了三层嵌套的READ TABLE BINARY SEARCH性能没有提升多少,维护起来却非常痛苦后来同事重构时花了两天时间才看懂。7.5 大内表筛选后的内存释放筛选之后原始大内表如果不再使用务必尽快释放。你可以在筛选后加上CLEAR lt_data或者FREE lt_data。这两个的区别在于CLEAR重置内表但保留初始内存分配FREE释放所有内存给系统。如果后续还要重新填充数据用CLEAR更合适如果彻底不再使用用FREE。释放内存对月度批处理这种连续跑多个程序的环境尤其重要——前一个程序的临时大表不释放后面程序的内存压力会越来越大系统可能出现扩展存储短缺。8. 一点实际体会优化内表筛选性能这件事给我最大的感受是不要迷信任何一个单一方案真正的关键在于理解数据量级、表类型和访问方式三者的匹配关系。你在5000行上用LOOP完全没问题到了80万行还用它就是在给自己制造隐患。排序表和哈希表各有优劣FILTER虽然顺手也要看版本和环境。我现在写新的报表程序一开始就会先估一下数据量级再决定用哪种方式做日期筛选。数据量超过十万行时默认直接上SORT 二分或FILTER 副键。同时我会在代码注释里写上性能测试的基准数据方便后续维护者理解为什么这么写而不是等程序跑不动了再返工。如果你正在优化一个因为内表日期筛选而卡顿的程序我的建议是先用GET TIME精确测出瓶颈在哪里再对照这篇文章的方案做一两次对比然后选一个最适合你系统和团队维护习惯的方式。踩过坑之后你就知道ABAP的性能优化很少是什么高深算法更多时候只是把该排序的排好序、该索引的建上索引、别在循环里做重复劳动就够了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑