You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spark读取Oracle表遇Decimal精度超限异常,求助排查

问题描述

我需要读取一张包含3500万行、上百列的Oracle表,使用以下Spark JDBC代码:

extractDF = spark.read \
.format("jdbc") \
.option("dbTable", f"""
    select tt.* from ( 
        select {ORIGIN_COLUMNS} from {ORIGIN_SCHEMA}.{ORIGIN_TABLE} t 
    ) tt 
    where tt.{ORIGIN_REF_COLUMN}<={int(origin_ref_column_value_max)} 
""") \
.option("driver", "oracle.jdbc.driver.OracleDriver" ) \
.option("url", ORACLE_URL) \
.option("user", ORACLE_USER) \
.option("password", ORACLE_PASSWORD) \
.option("fetchsize",f"{CONTROL_FETCH_SIZE}") \
.option("batchsize",f"{CONTROL_BATCH_SIZE}") \
.option("partitionColumn", ORIGIN_REF_COLUMN) \
.option("numPartitions", f"{numPartitions}") \
.option("lowerBound", f"{int(origin_ref_column_value_min)}") \
.option("upperBound", f"{int(origin_ref_column_value_max)+1}") \
.load()

extractDF.collect()  # --> Raise SparkArithmeticException: [DECIMAL_PRECISION_EXCEEDS_MAX_PRECISION] Decimal precision 31 exceeds max precision 11

表结构包含多个decimal类型列,示例如下:

root
 |-- COD_1: decimal(10,0) (nullable = true)
 |-- COD_2: decimal(11,2) (nullable = true)
 |-- COD_3: decimal(30,2) (nullable = true)
 |-- COD_4: decimal(7,2) (nullable = true)

我尝试将所有decimal类型转为Decimal(38,原小数位数),代码如下,但执行extractDF.collect()仍报相同错误:

decimalCols = [it for it in list(filter(lambda i: 'decimal' in i[1], extractDF.dtypes))]
for c in decimalCols:
    colScale = re.sub(r"decimal\(\d+,(\d+)\)", r"\1", c[1])
    extractDF = extractDF.withColumn(c[0], col(c[0]).cast(DecimalType(38, int(colScale))))

另外发现奇怪现象:rownum<42k时执行正常,rownum<44k时报错,而rownum在42k到44k之间时执行正常。环境为Spark 3.3.2、Scala 2.12、ojdbc8 23.2.0.0。

排查方案

1. 检查分区列的类型与精度冲突

你使用ORIGIN_REF_COLUMN作为分区列,且代码中对其做了int()转换。如果该列实际是Oracle的NUMBER类型且精度超过11(比如NUMBER(31,0)),Spark在分区计算时会尝试将其转为默认精度11的Decimal类型,直接触发溢出。

  • 验证分区列的Oracle原生类型:执行SQL查询
    SELECT DATA_TYPE, DATA_PRECISION, DATA_SCALE 
    FROM ALL_TAB_COLUMNS 
    WHERE TABLE_NAME='{ORIGIN_TABLE}' AND COLUMN_NAME='{ORIGIN_REF_COLUMN}'
    
  • 调整分区列处理逻辑:要么在JDBC查询中显式转换分区列精度(CAST({ORIGIN_REF_COLUMN} AS DECIMAL(38,0)) AS {ORIGIN_REF_COLUMN}),要么设置Spark参数spark.sql.decimalOperations.allowPrecisionLoss=true允许精度损失。

2. 提前指定列类型,避免Spark自动推断错误

当前的类型转换是在DataFrame加载后执行,但报错发生在collect()阶段,说明数据在JDBC读取时就已被错误解析为低精度Decimal。需要在读取阶段直接定义列类型:

  • 使用customSchema选项显式定义所有列类型:
    custom_schema = "COD_1 DECIMAL(10,0), COD_2 DECIMAL(11,2), COD_3 DECIMAL(38,2), ..."  # 补全所有列定义
    extractDF = spark.read \
    .format("jdbc") \
    .option("dbTable", "...") \
    .option("customSchema", custom_schema) \
    # 保留其他JDBC选项
    .load()
    
  • 或者在Oracle查询中直接转换Decimal列:SELECT CAST(COD_3 AS NUMBER(38,2)) COD_3, ... FROM ...

3. 定位特定行的异常数据

根据rownum的奇怪表现,说明42k到44k之间存在特殊数据(比如超出列定义精度的数值、科学计数法表示的值),导致Spark解析时临时提升精度触发报错。

  • 定位异常行:执行Oracle查询
    SELECT * 
    FROM (SELECT t.*, ROWNUM rn FROM {ORIGIN_SCHEMA}.{ORIGIN_TABLE} t) 
    WHERE rn BETWEEN 42000 AND 44000
    
    检查所有Decimal列的实际值,尤其是COD_3这类高精度列是否存在超出DECIMAL(30,2)的数值(比如超过9999999999999999999999999999.99的数值)。
  • 确认Oracle列的真实精度:执行SELECT COLUMN_NAME, DATA_PRECISION, DATA_SCALE FROM ALL_TAB_COLUMNS WHERE TABLE_NAME='{ORIGIN_TABLE}',排查列定义是否被修改过。

4. 调整JDBC驱动与Spark兼容性参数

ojdbc8 23.2.0.0属于较新版本,可能与Spark 3.3.2存在兼容性问题:

  • 添加JDBC选项oracle.jdbc.mapDateToTimestamp=false,避免日期类型转换间接影响Decimal解析。
  • 降低ojdbc版本至21.11.0.0(Spark 3.3.x推荐的稳定兼容版本),验证是否解决精度解析问题。
  • 显式设置spark.sql.decimal.maxPrecision=38,确保Spark启用最高精度的Decimal支持(默认值为38,显式配置可排除参数异常)。

5. 避免用collect()触发全量数据加载

collect()会将所有数据拉到Driver端,容易放大内存和类型解析问题,建议先通过采样或分区遍历验证:

# 采样部分数据验证类型转换效果
sampleDF = extractDF.sample(fraction=0.001).cache()
sampleDF.printSchema()
sampleDF.show()

# 按分区遍历处理,定位异常分区
for i in range(numPartitions):
    partitionDF = extractDF.filter(spark_partition_id() == i)
    try:
        partitionDF.write.mode("append").parquet("/tmp/test_partition")
    except Exception as e:
        print(f"Partition {i} error: {e}")

内容的提问来源于stack exchange,提问作者Barenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 16:17:45