当前位置: 首页 > article >正文

Oracle 批量投入数据方法总结

目录

  • 零. 待投入数据的表结构
  • 一. `INSERT INTO ... SELECT`投入数据
    • 1.1 普通的方式投入数据
    • 1.2 并行插入(Parallel Insert)投入数据
  • 二. PL/SQL 循环投入数据
    • 2.1 脚本介绍
    • 2.2 效果
  • 三. 💪PL/SQL FORALL 批量操作💪
    • 3.1 脚本介绍
    • 3.2 效果
  • 四. 💪SQL*Loader 工具加载外部文件💪
    • 4.2 效果


零. 待投入数据的表结构

create table DB_USER."PERSON_TABLE" (
  ID NUMBER not null
  , NAME VARCHAR2(50)
  , AGE NUMBER
  , EMAIL VARCHAR2(100)
  , CREATED_DATE DATE
)

在这里插入图片描述


一. INSERT INTO ... SELECT投入数据

🤔 INSERT INTO ... SELECT的这种方式相当于把数据加载到内存中之后再插入数据库,只适合投入小规模的数据。

1.1 普通的方式投入数据

⏹当数据量不是很多的时候,可以使用这种方式

  • 先从DUAL虚拟表中检索后造出指定条数的数据后,再插入到指定的表中。
  • 除了主键之类的关键字段之外,其余字段写固定值即可。
INSERT INTO PERSON_TABLE
	SELECT
		-- 因为该字段为字符串形式,所以使用TO_CHAR转换
		-- TO_CHAR(100000000 + LEVEL) || 'TEST_ID' AS id,
		LEVEL AS id,
		'Name_' || ROWNUM AS name,
        TRUNC(DBMS_RANDOM.VALUE(18, 60)) AS age,
        'user' || ROWNUM || '@example.com' AS email,
        SYSDATE - DBMS_RANDOM.VALUE(0, 365) AS created_date
	FROM
		DUAL 
	CONNECT BY LEVEL <= 1000000;

1.2 并行插入(Parallel Insert)投入数据

ALTER SESSION ENABLE PARALLEL DML;
INSERT /*+ PARALLEL(PERSON_TABLE, 4) */ INTO PERSON_TABLE
SELECT LEVEL AS id,
       'Name_' || ROWNUM AS name,
       TRUNC(DBMS_RANDOM.VALUE(18, 60)) AS age,
       'user' || ROWNUM || '@example.com' AS email,
       SYSDATE - DBMS_RANDOM.VALUE(0, 365) AS created_date
FROM DUAL
CONNECT BY LEVEL <= 1000000;

二. PL/SQL 循环投入数据

2.1 脚本介绍

  • 灵活,支持动态生成数据,适合中小数据量
  • 数据量大时性能较差,容易导致上下文切换开销
BEGIN
    FOR i IN 1..5000000 LOOP
        INSERT INTO PERSON_TABLE (id, name, age, email, created_date)
        VALUES (
            i,
            'Name_' || i,
            -- 随机年龄
            TRUNC(DBMS_RANDOM.VALUE(18, 60)),
            'user' || i || '@example.com',
            -- 随机日期
            SYSDATE - DBMS_RANDOM.VALUE(0, 365)
        );

        -- 每 100000 条提交一次
        IF MOD(i, 100000) = 0 THEN
            COMMIT;
        END IF;
        
    END LOOP;
    
    COMMIT;
END;
/

2.2 效果

⏹投入500万条数据,耗时5分钟。

在这里插入图片描述


三. 💪PL/SQL FORALL 批量操作💪

3.1 脚本介绍

  • 这种方式可以减少上下文切换,性能比普通的循环插入要好。
DECLARE
    TYPE person_array IS TABLE OF PERSON_TABLE%ROWTYPE;
    v_data person_array := person_array();
BEGIN
    FOR i IN 1..5000000 LOOP
        v_data.EXTEND;
        v_data(v_data.COUNT).id := i;
        v_data(v_data.COUNT).name := 'Name_' || i;
        v_data(v_data.COUNT).age := TRUNC(DBMS_RANDOM.VALUE(18, 60));
        v_data(v_data.COUNT).email := 'user' || i || '@example.com';
        v_data(v_data.COUNT).created_date := SYSDATE - DBMS_RANDOM.VALUE(0, 365);

        -- 每 100000 条批量插入一次
        IF MOD(i, 100000) = 0 THEN
            FORALL j IN 1..v_data.COUNT
                INSERT INTO PERSON_TABLE VALUES v_data(j);
            COMMIT;
            v_data.DELETE; -- 清空数组
        END IF;
    END LOOP;

    -- 插入剩余数据
    FORALL j IN 1..v_data.COUNT
        INSERT INTO PERSON_TABLE VALUES v_data(j);
    COMMIT;
END;
/

3.2 效果

⏹投入500万条数据,耗时1分钟18秒。

在这里插入图片描述


四. 💪SQL*Loader 工具加载外部文件💪

⏹写一个PowerShell脚本,根据数据库的表结构来生成csv文件

  • 该脚本执行后,会在桌面上生成一个csv文件。
# 文件名称
$file_name = 'person_data.csv'
# 路径
$outputFile = "$Home\Desktop\$file_name"
# csv 文件的总行数
$rows = 5000000
# 并行线程数
$threadCount = 4
# 每个线程生成的记录数量
$chunkSize = [math]::Ceiling($rows / $threadCount)

# 判断文件是否存在,存在的话就删除
if (Test-Path -Path $outputFile) {
    Remove-Item -Path $outputFile -Force
}

# 写入 CSV 表头
# "`"ID`",`"NAME`",`"AGE`",`"EMAIL`",`"CREATED_DATE`"" | Out-File -FilePath $outputFile -Encoding UTF8 -Append

# 定义脚本块
$scriptblock = {
    
    param($startRow, $endRow, $tempFile)

    # 在后台作业中定义 Generate-Chunk 函数
    function Generate-Chunk {
        
        param (
            [int]$startRow,
            [int]$endRow,
            [string]$filePath
        )

        $random = [System.Random]::new()
        $currentDate = Get-Date
        $sb = [System.Text.StringBuilder]::new()

		# 循环生成csv数据
        for ($i = $startRow; $i -le $endRow; $i++) {
			
			# =========================对应数据库的各字段值=========================
            $id = $i
            $name = "Name_$i"
            $age = $random.Next(18, 60)
            $email = "user$i@example.com"
            $createdDate = $currentDate.AddDays(- $random.Next(0, 365)).ToString("yyyy/MM/dd HH:mm:ss")
            # =========================对应数据库的各字段值=========================
			
			# =========================一行csv=========================
            $line = "`"$id`",`"$name`",`"$age`",`"$email`",`"$createdDate`""
            # =========================一行csv=========================

            $sb.AppendLine($line) | Out-Null
        }

        <# 
            将生成的内容写入文件
            -NoNewline 的作用是为了防止csv文件的最后一行被追加空行
        #>
        $sb.ToString() | Out-File -FilePath $filePath -Encoding UTF8 -Append -NoNewline
    }

    # 调用 Generate-Chunk 函数,多线程生成临时csv文件
    Generate-Chunk -startRow $startRow -endRow $endRow -filePath $tempFile
}

# CSV文件合成
function Merge-CSV {
        
	param (
		[string]$outputFile,
		[bool]$IsReadAllDataToMemory
	)
	
	# 获取所有分段文件,按名称排序
    $partFiles = Get-ChildItem -Path "$outputFile.*.part" | Sort-Object Name
	
	if ($IsReadAllDataToMemory) {
		
		# 将所有内容加载到内存中,然后一次性写入
		$partFiles | ForEach-Object { Get-Content $_.FullName } | Out-File -FilePath $outputFile -Encoding UTF8 -Force
		# 删除所有分段文件
		$partFiles | ForEach-Object { Remove-Item $_.FullName }
		
		return;
	}
	
	$partFiles | ForEach-Object {
        Get-Content -Path $_.FullName | Out-File -FilePath $outputFile -Encoding UTF8 -Append
        Remove-Item -Path $_.FullName
    }
}
    
try {
	# 定义job数组
    $jobs = @()
	
	# 组装job
    1..$threadCount | ForEach-Object {
		
        $startRow = ($_ - 1) * $chunkSize + 1
        $endRow = [math]::Min($_ * $chunkSize, $rows)
		
		# 临时csv文件
        $tempFile = "$outputFile.$_.part"

        $jobs += Start-Job -ScriptBlock $scriptblock -ArgumentList $startRow, $endRow, $tempFile
    }
	
	# 统计生成csv文件所消耗的时间
    $exec_time = Measure-Command {

        Write-Host "临时csv文件开始生成..."

        # 执行job,等待并收集所有执行结果
        $jobs | ForEach-Object { Wait-Job -Job $_; Receive-Job -Job $_; Remove-Job -Job $_ }

        # 合并所有并发生成的csv临时文件,组装成最终的总csv文件
        Write-Host "临时csv文件生成完毕,开启合并..."
        Merge-CSV -outputFile $outputFile -IsReadAllDataToMemory $False
    }

    Write-Host "csv文件生成完毕,共消耗$($exec_time.TotalSeconds)秒: $outputFile" -ForegroundColor Red
	
} catch {

    # 当异常发生时,清空桌面上的临时csv文件
    if (Test-Path -Path "$outputFile.*.part") {
        Remove-Item -Path "$outputFile.*.part" -Force
    }

    Write-Host "脚本运行时发生异常: $_" -ForegroundColor Red
    Write-Host "详细信息: $($_.Exception.Message)" -ForegroundColor Yellow
    Write-Host "堆栈跟踪: $($_.Exception.StackTrace)" -ForegroundColor Gray
}

Read-Host "按 Enter 键退出..."

⏹创建控制文件control_file.ctl

LOAD DATA
INFILE 'person_data.csv'
INTO TABLE PERSON_TABLE
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(id, name, age, email, created_date "TO_DATE(:created_date, 'YYYY/MM/DD HH24:MI:SS')")

⏹使用 SQL*Loader 执行加载

  • 性能极高,适合大规模数据插入。
  • 支持多线程和并行加载。
sqlldr db_user/oracle@SERVICE_XEPDB1_CLIENT control=control_file.ctl direct=true

4.2 效果

⏹投入500万条数据,耗时居然不到10秒!💪💪💪

在这里插入图片描述


http://www.kler.cn/a/502147.html

相关文章:

  • 当当网热销书籍数据采集与可视化分析
  • 浅谈云计算06 | 云管理系统架构
  • RTDETR融合[WACV 2024]的MetaSeg中的gmb模块
  • webpack打包要义
  • 【电子通识】PWM驱动让有刷直流电机恒流工作
  • C语言的小项目-简易计算器
  • SQL进阶实战技巧:统计用户的累计消费金额及VIP等级?
  • [Effective C++]条款45 运用成员函数模板接受所有兼容类型
  • 如何使用 Java 的 Spring Boot 创建一个 RESTful API?
  • c++ 中的容器 vector、deque 和 list 的区别
  • 穿越火线怀旧服预约网页vue3版本
  • JavaScript 类型转换
  • EFK采集k8s日志
  • 【OpenGL/C++】面向对象扩展——测试环境
  • FlashAttention的原理及其优势
  • HTTP/HTTPS ④-对称加密 || 非对称加密
  • 使用WeakHashMap实现缓存自动清理
  • 特制一个自己的UI库,只用CSS、图标、emoji图 第二版
  • MySQL Binlog 同步工具go-mysql-transfer Lua模块使用说明
  • Django创建数据表、模型、ORM操作
  • 饿汉式单例与懒汉式单例模式
  • 前端学习-事件对象与典型案例(二十六)
  • 25/1/13 算法笔记<嵌入式> 继续学习Esp32
  • uiautomator2 实现找图点击
  • 记一次学习skynet中的C/Lua接口编程解析protobuf过程
  • FreeSWITCH Sofia SIP 模块常用命令整理