☰
Doris/StarRocks导入报错too many filtered rows的排查与处理
2026/10/1 5:30:46 网站建设 项目流程

导入失败,报错: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 Label='your_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.50

shop_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 表,某个字段长期允许 NULL,MySQL 里存了 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指定列分隔符默认是 \t,CSV 文件用逗号时应显式指定
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?file=xxx"

如果端口不通,就得联系运维把 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 条件把 status='cancel' 的记录筛掉。这些行不会算进 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='', encoding='utf-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(f"line {lineno}: {row} -> {e}") print(f"total={total}, bad={bad}, bad_ratio={bad / max(total, 1):.4f}")

这个脚本虽然只覆盖了常规场景,但已经能把绝大多数类型转换和日期格式问题提前暴露出来。如果脚本显示的 bad_ratio 大于 0,我就知道这批数据不能直接导入,得回到上游清洗。如果你导的是 JSON 文件,也可以用类似思路做 schema 校验。

我自己的体会是,处理这种导入报错最忌讳的就是“看见报错就调参”。先拉 ErrorURL,再分析脏数据,最后才决定是修数据、修表还是调容错阈值。这套流程走顺之后,我再也没有因为 too many filtered rows 熬过夜。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询