
导入失败报错too many filtered rows xxx后面还跟着一个 ErrorURL。这条报错我在近几年的数据接入工作里碰到过很多次。它最常见于 Doris 或 StarRocks 的数据导入链路不管你是用 Stream Load、Broker Load 还是 Routine Load只要任务失败返回里有 ErrorURL 这个字段基本就是这类分布式分析型数据库在提示你导入解析后被过滤掉的脏数据行数超过了系统允许的阈值而系统默认这个阈值是 0。换成人话就是——你有脏行一行都不许有所以整个导入任务被中止了。这篇文章就专门拆这个报错从原因定位到实操修复一次性说清楚。1. 报错拆解too many filtered rows 到底在说什么1.1 这个报错最常出现在哪类导入场景很多人一看到“导入失败”就以为是 Excel 文件太大、格式不对、或者数据库连接问题但实际上这条报错和数据量大小没有直接关系而是和“数据质量”直接相关。Doris、StarRocks 这类系统的导入流程大体上是这样的客户端把文件交给 FE 节点FE 把导入事务拉起来然后把数据分发到多个 BE 节点上做解析、转换、分区分桶路由。BE 节点在扫描数据时会逐行判断这条数据能不能落到目标表里。能落进去的算 loaded rows落不进去的算 filtered rows。扫描结束后系统会算一个比例filtered rows 除以总行数。如果这个比例超过了导入任务设定的 max_filter_ratio任务就会失败。默认情况下max_filter_ratio 是 0也就是只要有一行落不进去任务就整体失败。所以你会看到这样的错误信息too many filtered rows, max_filter_ratio 0.0这里的 xxx 在 Doris 的返回消息里通常是 max_filter_ratio 的具体值或者是过滤行数相关的描述。无论长什么样核心含义都一样脏数据行数超过了容错上限。这个报错和“表不存在”“权限不足”“磁盘满”这类环境型报错不一样它属于数据型报错意思是数据本身有问题但系统不告诉你具体是哪几行只告诉你“有而且不少”。这时候你如果只盯着 max_filter_ratio 调参数问题往往还会反复出现。1.2 报错里的 ErrorURL 是干什么的很多人在报错信息里看到 ErrorURL 一头雾水以为是什么风险链接。实际上这是 Doris 和 StarRocks 给你留的错误日志下载地址。当导入任务因为过滤行过多失败时BE 节点会把异常行的信息写到一个错误日志文件里然后在返回结果中带一个 URL指向这个文件。你把这个 URL 复制到浏览器或者用 curl 命令访问就能下载到包含具体过滤原因的日志。ErrorURL 长得大概是这样的http://10.10.10.10:8040/api/_load_error_log?file__shard_0/error_log_20250414_100000其中 8040 是 BE 节点默认的 WebServer 端口后面跟的 file 参数是错误日志文件的标识。下载下来的日志里通常能看到异常行的行号、原始数据内容、以及被过滤的具体原因。在有些场景下你也可以不用 ErrorURL。比如用 Broker Load 导数据时直接在 FE 上执行SHOW LOAD WARNINGS FROM your_db WHERE Labelyour_label;也能看到类似的信息。但 Stream Load 场景里最直接的方式还是拉取 ErrorURL。注意错误日志文件不会永久保留BE 会按自己的生命周期规则清理所以报错后最好尽快下载不要等到第二天再查。2. 一条数据被 filter 的常见原因2.1 类型转换失败是最常见的过滤原因导入时BE 会把原始字符串转换成目标列的类型。如果你的 CSV 某一列写的是 abc而表结构里这一列是 BIGINT那这一行大概率会进过滤通道。我举个具体例子假设目标表结构长这样CREATE TABLE ods_sales ( id BIGINT, shop_id INT, sale_date DATE, amount DECIMAL(12,2) ) DUPLICATE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 10;然后你的 CSV 文件里有一行是10001,SHOP_A,2025-04-14,99.50shop_id 明明是 INT但文件里写的是字符串 SHOP_A这行就会因为类型转换失败被过滤掉。日期字段更是重灾区。Doris 对 DATE 格式有明确要求比如 2025-04-14 这种是合法的但 2025/04/14、20250414、2025年4月14日这类写法在一些配置下会被直接过滤。数字字段里如果出现空字符串、半角逗号、千分位符号也可能被过滤。类型失败的过滤原因在错误日志里通常写得很直白类似 invalid number format、date parse error、column type mismatch 这类对照着行号去找原始文件基本一眼就能看出来。2.2 分区或分桶条件不满足也会被过滤这可能是最容易忽略的一个原因。如果你的表用了范围分区比如按 sale_date 分区而导入的数据里有一条 sale_date 是 2024-01-01但你的表里只建了 2025 年的分区这条数据就不知道往哪个分区里放。系统不会自动帮你建分区它会选择把这行当作过滤行处理。同样的问题也出现在分桶键上。如果分桶键的值超过了正常范围或者分桶键字段本身是 NULL而表结构又不允许 NULL这行也会被过滤。遇到这类问题不要只盯着数据格式看先查一下表的分区定义SHOW PARTITIONS FROM ods_sales;然后用导入文件里的维度值比对一下分区范围确认所有数据都能落到已有分区里。如果确实有历史数据要导就先把对应分区建好再导否则无论你调多少次 max_filter_ratio数据都进不去。2.3 字段缺失、NULL 与不可为空列的冲突这是另一个高频过滤原因。比如你的 CSV 是标准的逗号分隔但有一行少了一列导致后续所有列的内容都往左偏移了一位。原本应该是 amount 的 99.50跑到下一个字段去了最后那列变成空值。如果目标表里的 amount 列是 NOT NULL那这行就会因为 NULL 约束被过滤。还有一种情况是JSON 格式导入时某条记录缺少了某个字段。如果这个字段在表里是必须存在的那这条记录基本必被过滤。错误日志里通常会写 column value is null 或者 field not found。我在实际排查中见过一个很典型的场景上游是一张 MySQL 表某个字段长期允许 NULLMySQL 里存了 NULL导到 Doris 时没有做处理结果每次同步都有一批行因为 NULL 被过滤。这种问题靠调参数解决不了必须在上游做 coalesce 转换把 NULL 变成默认值。2.4 编码、分隔符、表头等文件级问题脏数据不一定只在单元格内容里文件本身的格式也可能制造大量过滤行。最常见的坑是 CSV 编码。如果上游导出的文件是 GBK 编码而 Doris 默认按 UTF-8 解析那一行里只要有中文解析出来就是乱码跑到目标字段里很可能因为非法 UTF-8 序列被过滤。而且这种问题通常不是一行两行是整个文件大批量过滤。处理方式很简单导入前先转码iconv -f gbk -t utf-8 sales.csv sales_utf8.csv还有表头问题。如果文件第一行是 id,shop_id,sale_date 这类表头直接从第一行开始导入表头就会变成数据。字段少的表还好字段多了以后整行列错位过滤率能到 90% 以上。我一般建议数据上游不要生成表头或者在导入前用 sed 把第一行删掉sed -i 1d sales.csv如果你在列映射里写了 columns 参数也可以尝试用 where 条件把表头过滤掉但最省事的还是“源头不产表头”。3. 实操复盘从拿到报错到定位脏数据3.1 一次完整的 Stream Load 失败记录我拿一个真实场景来演示整个排查过程。假设我有一条 Stream Load 任务把一份名为 sales.csv 的文件导入到 ods_sales 表。命令大概是这样的curl --location-trusted -u root: \ -H label:load_sales_20250414_01 \ -H column_separator:, \ -H columns:id,shop_id,sale_date,amount \ -T sales.csv \ http://fe_host:8030/api/db_name/ods_sales/_stream_load导入很快失败了返回的 JSON 大概长这样{ TxnId: 10086, Label: load_sales_20250414_01, Status: Fail, Message: too many filtered rows, max_filter_ratio 0.0, NumberTotalRows: 10000, NumberLoadedRows: 0, NumberFilteredRows: 327, NumberUnselectedRows: 0, LoadBytes: 2456789, ErrorURL: http://10.0.0.8:8040/api/_load_error_log?file__shard_0/error_log_20250414_123456 }看到这个结果第一反应不是去看 max_filter_ratio而是去点那个 ErrorURL。3.2 从 ErrorURL 取出具体异常行我用 curl 直接拉取错误日志curl http://10.0.0.8:8040/api/_load_error_log?file__shard_0/error_log_20250414_123456 -o load_error.log下载下来的文件内容不完全一样取决于版本但大致格式类似下面这样每行记录一条原始数据和被过滤的原因line 12, column shop_id, reason: invalid number format, data: 10001|SHOP_A|2025-04-14|99.50 line 58, column sale_date, reason: date parse error, data: 10002|1001|2025/04/14|50.00 line 91, column amount, reason: decimal overflow, data: 10003|1002|2025-04-14|99999999999999.99看完日志就清楚了。327 行里一部分是 shop_id 写成了字符串一部分是日期斜杠格式还有一部分是 amount 超出精度。这几种问题混合在一起导致了导入失败。这种现场信息比任何玄学的排查方法都管用。没有 ErrorURL 的时候你可能要对着 CSV 手动翻半天拿到 ErrorURL 之后所有脏数据都摆在桌面上。3.3 三种修复方向改数据、改表、调整容错定位到具体原因之后修复方向一般有三种。第一种是改数据。如果是日期格式不统一、shop_id 混入了非数字字符就得回到上游把这些字段清洗干净。日期最好统一成 2025-04-14数字字段不要带单位不要加千分位符号空字符串要么补默认值要么转成合法 NULL。第二种是改表结构。如果某个字段确实需要存更长字符串或者 DECIMAL 精度确实不够就调整表结构。但说实话改表只是为了迁就脏数据不是长久之计能改上游尽量改上游。第三种是设置 max_filter_ratio。如果这份数据本身可以容忍一定比例的脏行比如 10000 行里有 20 行是废弃记录丢了不影响业务分析那就在导入参数里把容错比例调大一点。Stream Load 的写法是在 HTTP header 里加-H max_filter_ratio:0.02表示允许 2% 的过滤比例。327 / 10000 大概是 3.27%如果我不想清数据就得把 ratio 调到 0.04 以上。但我的习惯是先看清数据长什么样再决定要不要容忍而不是一上来就无脑调大比例。4. 参数怎么配才不会误伤4.1 max_filter_ratio 配多少合适这个参数的本意是给导入任务加一层“保险丝”防止偶发脏数据导致任务中断而不是让你把大量脏数据直接吞掉。判断是否满足成功条件可以在心里估算这个公式NumberFilteredRows / NumberTotalRows max_filter_ratio举个例子总行数 100000过滤行数 500那过滤比例是 0.5%。如果你设置 max_filter_ratio 为 0.01也就是 1%任务就会成功500 行脏数据会被悄悄丢弃。但是我强烈建议你设置这个参数之前先看看被丢弃的是什么数据。如果丢掉的是核心交易记录哪怕比例只有 0.1%也可能造成数据统计偏差。这时候的正确做法不是调大比例而是回去把数据修好。如果确认脏数据确实无伤大雅一般设置 0.01 到 0.05 就够了也就是允许 1% 到 5% 的过滤率。不太建议直接设置成 1因为那等于允许所有行都失败任务虽然显示成功但真正导入的行数可能是 0属于典型的“假成功”。4.2 strict_mode 的作用与误区很多人会把 max_filter_ratio 和 strict_mode 搞混其实它们管的事不一样。strict_mode 控制的是导入时的类型转换严格程度。严格模式下数据格式和目标列类型不匹配时会直接把这行标记为过滤非严格模式下系统会尝试做一些默认转换比如把空字符串转成 0或者把无法转换的值替换成默认值。看到这你可能觉得那把 strict_mode 关掉不就能减少过滤了吗未必。非严格模式做的是“尽力转换”但如果值本身完全无法转换比如字符串 abc 转数值该过滤还是会过滤。它只是能救回来一部分格式擦边球的数据。所以我的建议是生产环境从上游同步数据时如果数据质量可控保持默认的配置就够了不要把 strict_mode 当作兜底方案。真正能减少过滤行的是上游的数据质量。4.3 其他常用导入参数速查除了 max_filter_ratio 和 strict_mode处理导入报错时还会经常用到这几个参数。参数作用备注label导入任务唯一标识每次导入建议用不同 label方便追踪任务状态columns指定源文件列与表列的映射关系源文件列顺序与表不一致时必填where对转换后的数据进行条件过滤可以提前过滤不需要的行column_separator指定列分隔符默认是 \tCSV 文件用逗号时应显式指定line_delimiter指定行分隔符默认是 \n特殊文件可能需要调整timeout导入超时时间大文件导入时可适当调大其中 columns 和 where 组合使用能在一定程度上把脏行在进入表结构前处理掉。比如某一行明显是表头你可以在 columns 映射后加一个 where 条件把这些行过滤在统计之外。不过我还是那句话源头干净比导入阶段拼命兜底要舒服得多。5. 常见问题与避坑技巧5.1 ErrorURL 访问不了或者打不开怎么办这个我踩过坑。有一次报错里明明带着 ErrorURL但我在本地浏览器打开就是超时后来才发现 BE 节点的 8040 端口只对内网开放我在跳板机上访问不了。如果你也遇到 ErrorURL 打不开的情况先看两件事。第一确认网络通不通。在能访问集群的机器上执行curl -v http://BE_HOST:8040/api/_load_error_log?filexxx如果端口不通就得联系运维把 BE WebServer 端口加入访问白名单。第二错误日志文件可能已经被清理。如果下载时提示文件不存在可以试试用 SHOW LOAD 命令查看任务信息SHOW LOAD WHERE Label your_label;返回结果里一般也有 URL 字段有时能拿到新的下载地址。再不行就检查上游数据自查毕竟远程日志的目的是快速定位不是唯一手段。5.2 设置了 max_filter_ratio 照样失败如果确认设置了 max_filter_ratio任务还是报 too many filtered rows通常是这几种原因。第一种是过滤比例算出来比设置的还高。比如你设置了 0.01但实际过滤了 3% 的行那肯定还是失败这不是参数没生效而是参数值不够大。第二种是文件级解析错误。max_filter_ratio 只管行级过滤如果文件本身语法有问题比如 JSON 文件不是合法的 JSON、CSV 的引号没闭合、分隔符配错了这类错误可能导致整个任务直接失败不走行过滤逻辑。第三种是配置位置不对。Stream Load 要写在 HTTP header 里Broker Load 要写在 PROPERTIES 里。如果你写在 SQL 结尾或者写错了地方系统不会报配置错误但也不会生效容易让人误以为参数没用。所以排查时别急着怀疑系统先用 SHOW LOAD 看任务详情确认过滤行数和总行数再回头检查自己的参数到底写对没有。5.3 过滤行数看着很怪NumberUnselectedRows 是什么返回 JSON 里除了 NumberFilteredRows还有一个 NumberUnselectedRows很多人会混淆。NumberUnselectedRows 是被 where 条件主动排除掉的行数。这些行不是数据错误而是你自己设定规则不要它们比如 where 条件把 statuscancel 的记录筛掉。这些行不会算进 loaded rows但一般也不会计入 max_filter_ratio 的过滤比例里。如果你发现总行数里有一批“神秘消失”的行先看看是不是 where 条件太宽把有效数据也筛掉了。有时候这种“消失”比过滤更隐蔽因为它不报错任务还是成功状态但落库行数少了。5.4 长期方案导入前加一道预清洗被 too many filtered rows 反复折磨之后我终于意识到一个道理在数据库导入阶段处理脏数据永远是事倍功半的。更靠谱的做法是把脏数据拦截在导入之前。我现在习惯在批量导入前跑一个简单的 Python 脚本对 CSV 做一轮预检查。不求检查得多严密只要把类型转换、必填字段、日期格式这三大高频问题扫一遍就够。import csv import sys from datetime import datetime file_path sys.argv[1] total 0 bad 0 with open(file_path, newline, encodingutf-8) as f: reader csv.reader(f) for lineno, row in enumerate(reader, 1): total 1 try: int(row[0]) # id int(row[1]) # shop_id datetime.strptime(row[2], %Y-%m-%d) # sale_date float(row[3]) # amount except Exception as e: bad 1 if bad 10: print(fline {lineno}: {row} - {e}) print(ftotal{total}, bad{bad}, bad_ratio{bad / max(total, 1):.4f})这个脚本虽然只覆盖了常规场景但已经能把绝大多数类型转换和日期格式问题提前暴露出来。如果脚本显示的 bad_ratio 大于 0我就知道这批数据不能直接导入得回到上游清洗。如果你导的是 JSON 文件也可以用类似思路做 schema 校验。我自己的体会是处理这种导入报错最忌讳的就是“看见报错就调参”。先拉 ErrorURL再分析脏数据最后才决定是修数据、修表还是调容错阈值。这套流程走顺之后我再也没有因为 too many filtered rows 熬过夜。