大数据写入到Oracle数据库(批量插入数据)

article/2025/9/13 20:53:22

    开发中经常遇到批量插入数据的需求,为了提高开发效率大多会使用ORM架构,个别之处 才会手写SQL,我们使用C#.NET Core5.0开发,所以优先选择了微软的EF。 

    但是EF原生没有批量操作功能,需要自己扩展或使用第三方的扩展,由于使用第三方扩展怕有风险,因此全部自己手写批量插入和更新。

    一段时间后数据多了,这里发现EF查询性能较差,2百万条数据就开始慢得不想用喽,后来换成SqlSugar(下面简称SS),SS的查询确实与原生SQL差不多,很好,很强大,但是它自带的批量插入就不太好用喽,测试后发现性能与官方宣传的差距太大。

 

本文实测并记录了Oracle 11g r2 单张表113列,批量插入1万条数据的结果:

方法一:

描述 :使用参数数组插入(如果看不懂我的描述,可以继续向下看源代码);

耗时:插入1万条1.729秒

 

方法二        :

描述 :使用OracleBulkCopy插入;

耗时:插入1万条2.672秒

方法三      :

描述 :使用sugar的Fastest.BulkCopy插入;

耗时:插入1万条4.532秒        

以上3个方法多次测试每次耗时略有差异,但基本一致 。

sugar的其他批量插入方法:

经测试只适合插入100条以内的数据,插入数据生成 INSERT ALL  INTO Table() VALUES()语句,个人觉得大量数据不适用,因为数据插入过多后代码会耗尽数据库服务器的cpu,导致无响应的问题

db.Insertable(list).ExecuteCommand();

下面这种就更不行了,是一条一条插入的:

db.Insertable(targetList).UseParameter().ExecuteCommand();

29d839c9e3144dd9ba775d07189d5e06.png

 

方法一代码:

以下代码根据项目手工编写,经测试性能是最高的,就本文测试的环境下远远高于sqlsugar的性能  。

/// <summary>/// 批量插入;要么全部成功要么全部失败/// </summary>/// <typeparam name="T"></typeparam>/// <param name="list"></param>/// <returns></returns>public static int BulkAdd<T>(List<T> list){Type model = typeof(T);List<PropertyInfo> pi = GetMappedField<T>();//生成数据源与参数OracleParameter[] paras = new OracleParameter[pi.Count];//用于存放数据for (int i = 0; i < pi.Count; i++){PropertyInfo p = pi[i];string name = p.Name;Type pt = p.PropertyType;//bool t1 = pt.IsGenericType;//Console.WriteLine($"序号:{i + 1};字段{ p.Name};类型{ pt.Name };IsGenericType:{pt.IsGenericType}");//if (name == "LLCLY")//{//    string name2 = p.Name;//}//此字段是否可可为空bool IsNullable = pt.Name == "Nullable`1";var colArr = new object[list.Count];for (var rowIndex = 0; rowIndex < list.Count; rowIndex++){object val = p.GetValue(list[rowIndex], null);//时间或整形不能为nullif (IsEnum(p)){//枚举单独处理colArr[rowIndex] = val == null ? DBNull.Value : val.GetHashCode();//Console.WriteLine($"序号:{i + 1};字段{ p.Name};类型{ pt.Name };值{val};hash:{val.GetHashCode()}");continue;}else{if (IsNullable){bool teste1 = pt.DeclaringType != null && pt.DeclaringType.IsEnum;bool teste2 = p.DeclaringType != null && p.DeclaringType.IsEnum;bool teste3 = pt.GetGenericArguments()[0].IsEnum;if (IsEnum(p)){colArr[rowIndex] = (val == null || !IsNullable) ? DBNull.Value : val.GetHashCode();}//Console.WriteLine($"序号:{i + 1};字段{ p.Name};类型{ pt.Name }-{pt.GetGenericArguments()[0].Name};值{val}");}else{colArr[rowIndex] = (val == null && !IsNullable) ? 0 : val;//Console.WriteLine($"序号:{i + 1};字段{ p.Name};类型{ pt.Name };值{val}");}}}//基本类型OracleDbType dt = OracleDbType.Varchar2;if (p.PropertyType.Namespace == "System"){if (IsNullable){//可为空时找真实基本类型pt = pt.GetGenericArguments()[0];}switch (pt.Name){case "String":dt = OracleDbType.Varchar2;break;case "Short":case "Int":case "Int16":dt = OracleDbType.Int16;break;case "Int32":dt = OracleDbType.Int32;break;case "Decimal":dt = OracleDbType.Decimal;break;case "Long":case "Int64":case "Double":dt = OracleDbType.Long;break;case "DateTime":dt = OracleDbType.Date;break;default:break;}}else if (p.PropertyType.IsEnum || pt.BaseType.Name == "Enum" || p.PropertyType.BaseType.Name == "Enum"){//枚举单独处理dt = OracleDbType.Int32;}paras[i] = new OracleParameter($":{name}", dt) { Value = colArr };}//获取表名string tableName = model.Name;TableAttribute[] arrDesc = (TableAttribute[])model.GetCustomAttributes(typeof(TableAttribute), false);if (arrDesc.Length > 0){//EF架构的表属性名tableName = arrDesc.First().Name;}else{//sqlsugar架构的表属性名SugarTable[] tableDesc = (SugarTable[])model.GetCustomAttributes(typeof(SugarTable), false);tableName = tableDesc.First().TableName;}//获取字段string[] propertys = pi.Select(o => o.Name).ToArray();string[] paras_propertys = propertys.Select(o => $":{o}").ToArray();int result = 0;string sql = $"INSERT INTO {tableName}({string.Join(",", propertys)}) VALUES({string.Join(",", paras_propertys)})";using (OracleConnection oracleConnection = new(Conn)){oracleConnection.Open();using (var command = oracleConnection.CreateCommand()){command.ArrayBindCount = list.Count;command.FetchSize = 1000;command.CommandText = sql.ToString();command.CommandType = CommandType.Text;command.Parameters.AddRange(paras);command.BindByName = true;result = command.ExecuteNonQuery();}}return result;}public static bool  IsEnum(PropertyInfo p) {Type pt = p.PropertyType;return pt.IsEnum|| pt.BaseType.Name == "Enum"//标记可为空的枚举  || (pt.GetGenericArguments().Length > 0 && pt.GenericTypeArguments.First().IsEnum);//|| (pt.GetGenericArguments().Length > 0 && pt.GetGenericArguments()[0].IsEnum);}/// <summary>/// 获取映射的字段/// </summary>/// <returns></returns>public static List<PropertyInfo> GetMappedField<T>() {Type model = typeof(T);List<PropertyInfo> proTemp = model.GetProperties(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public).ToList();//过滤出与表对应的字段List<PropertyInfo> pi = new List<PropertyInfo>();for (int i = 0; i < proTemp.Count; i++){PropertyInfo p = proTemp[i];string name = p.Name;Type pt = p.PropertyType;//是否泛型 如:List<User>   Int?  等//bool t1 = pt.IsGenericType;//Console.WriteLine($"序号:{i + 1};字段{ p.Name};类型{ pt.Name };IsGenericType:{pt.IsGenericType}");if (pt.BaseType == null){continue;}//被标记不与数据库映射的字段SugarColumnSugarColumn[] caArray = (SugarColumn[])p.GetCustomAttributes(typeof(SqlSugar.SugarColumn), true);if (caArray.Any(a => a.IsIgnore == true)){continue;}Object[] caArray2 =  p.GetCustomAttributes(typeof(NotMappedAttribute), true);if (caArray2.Length > 0){continue;}//基本类型if (p.PropertyType.Namespace == "System"){pi.Add(p);}else if (p.PropertyType.IsEnum || pt.BaseType.Name == "Enum" || p.PropertyType.BaseType.Name == "Enum"){pi.Add(p);}else{//不与数据库映射的字段continue;}}return pi;}

方法二代码:

 /// <summary>/// 批量插入数据/// [有人说,会自动创建多余的列,还可能多一些数据,且删不掉,但是我没遇到过]/// </summary>/// <param name="table">数据表</param>/// <param name="targetTableName">数据库目标表名</param>/// <returns></returns>public static  bool ExcuteBulkData(DataTable table, string targetTableName=null){bool result = false;using (OracleConnection conn = new(Conn)){conn.Open();using (OracleBulkCopy bulkCopy = new(Conn, OracleBulkCopyOptions.Default)){if (table != null && table.Rows.Count > 0){bulkCopy.DestinationTableName = targetTableName?? table.TableName;bulkCopy.BatchSize = table.Rows.Count;for (int i = 0; i < table.Columns.Count; i++){string col = table.Columns[i].ColumnName;bulkCopy.ColumnMappings.Add(col, col);}bulkCopy.BulkCopyOptions = new() {};bulkCopy.WriteToServer(table);result = true;}}}return result;}

方法三源代码:

引用过sqlsugar后只需要下面一行代码即可实现批量插入,但是性能有限 。

sugar.db.Fastest<CardDel>().BulkCopy(targetList);

 

总结:

1、方法一适合所有场景 ;方法二可根据情况选择,性能略低于第一种;sqlsugar的批量插入根据情况选择,性能并没有官网宣传的那么高,以上经验全部来源于个人实践后总结,供大家参考 。

2、平时要多自己写实例测试才能得到真实的结果,不能只看官网的宣传。

 


http://chatgpt.dhexx.cn/article/YiMwT8yA.shtml

相关文章

Mybatis如何批量插入数据?

有一小段时间没有写技术博文了&#xff0c;今天我又来更新啦&#x1f60a;&#x1f60a;&#x1f60a;5月份中间有个小插曲&#xff0c;那就是我在单位打球意外导致脚跟腱受伤&#xff0c;然后住院在家修养了一个多月了&#xff0c;所以小伙伴们在外锻炼时还是时刻注意自身的安…

MYSQL-批量插入数据

批量插入数据 存储过程也有返回值&#xff0c;存储过程有一个或多个返回值&#xff0c;函数有且只有一个返回值 往表里插入1000w数据 1.建表 create database bigData; use bigData;create table dept( id int unsigned primary key auto_increment, deptno mediumint unsi…

Mybatis批量插入数据

前言 在很多业务场景中&#xff0c;我们需要批量录入数据。那么意味着我们需要以最高效的方式去实现功能&#xff0c;同时也需要保证软件的便捷性与可维护性&#xff0c;开源字节使用MyBatis foreach标签方式优雅的实现了材料的出入库。源码开放&#xff0c;可前往码云仓库免费…

批量插入

目录 一、批量插入数据 ⚪JDBC的批量处理语句的方法 二、高效的批量插入 1.举例&#xff1a;向goods表中插入20000条数据 ⭐goods表的创建 方式一&#xff1a;使用Statement 方式二&#xff1a; 使用PreparedStatement替换Statement &#x1f319;方式一与方式二的对比…

三种批量插入数据的方法

批量插入数据 本文将介绍三种批量插入数据的方法。第一种方法是使用循环语句逐个将数据项插入到数据库中&#xff1b;第二种方法使用的是SqlBulkCopy&#xff0c;使您可以用其他源的数据有效批量加载 SQL Server 表&#xff1b;第三种使用的方法是sql server中的表值参数方法&a…

Vue 通过a标签下载文件

vue前端点击某个a便签需要下载对应文件&#xff0c;href为下载路径&#xff0c;download为下载文件名 这是Vue-cli3 写法 因为public文件夹是静态拷贝。 并不要把文件放到src>assets里面 因为assets中的文件会经过 webpack 打包&#xff0c;重新编译。 <a href"./p…

通过 a 标签下载文件

后台管理项目涉及到文件下载到本地&#xff0c;类型包括&#xff08;图片&#xff0c;音频&#xff0c;视频&#xff0c;office文件等等&#xff09;&#xff0c;只需将后台接口提供的url给到 a 标签即可。 <div class"preview-download" click.stop"downLoa…

a标签下载文档 a下载文档失败问题 跨域调用

可以通过a标签下载文档 a中有一个download属性 这个属性可以为空&#xff0c;如果写入的话这是写下载文档的名字 a标签默认下载是在同一个域内&#xff0c;如果跨域的话下载会失败&#xff0c;可能变成预览 出现的问题 from origin ‘http://localhost:8080’ has been blocke…

js使用a标签实现文件下载功能

工作中遇到下载txt文本文件的下载需求&#xff0c;稍作整理&#xff0c;下载示例如下&#xff1a; <button onclick"log()">文件下载</button>// 日志函数 function log() {// dom中创建a标签let aTag document.createElement(a)// 日志展示的内容let c…

JS-a标签下载文件

目录 问题描述解决方法拓展 另一种文件流下载 问题描述 昨天拿到一个需求,就是做成这种下载的模样 后台返回的下载链接长成这样 解决方法 就是一个最普通的方法,创建a标签 ,加上download属性,模拟标签点击 就可以触发下载了 注意:这个是因为直接拿到了请求链接,get请求 co…

HTML中的一些细节处理

# 说明 参考资料:简书的 伴歌知行的JS下载图片和文件&#xff0c;防止浏览器直接打开 、夜半修仙,;CSDN的weixin_3791475的使用a标签下载文件不跳转;努力学习的汪&#xff1a;洪学习笔记 文章目录 # 说明前端利用a标签实现文件[图片]下载文件下载常用方式总结分析Ⅰ-后端设置下…

JAVA中数组和集合的区别

转换 数组转换为集合&#xff1a; Arrays.asList(数组) 示例&#xff1a; 1 2 3 4 5 int[] arr {1,3,4,6,6};//定义一个长度为5数组 Arrays.asList(arr); for(int i0;i<arr.length;i){ System.out.println(arr[i]); } 集合转换为数组&#xff1a; 集合.toArray()…

JAVA中数组和集合的相互转换

数组转集合&#xff1a; 1.遍历&#xff0c;最常用的方法&#xff0c;但是过程会繁琐一点 int arrs[] {1, 2};//1.遍历List<Integer> list new ArrayList<>();for (int ele : arrs) {list.add(ele);}System.out.println(list);2.使用数组工具类的asList()方法 但…

数组与集合的区别及知识拓展

一、数组和集合的区别 1、数组的长度是固定的&#xff0c;一个数组只能存储一种类型的元素&#xff0c;可以存储任意类型。 2、集合的长度是可变的&#xff0c;存储引用数据类型&#xff0c;当存储基本数据类型时需要存储其对应的包装类。 二、知识拓展 1、基本数据类型所对应…

数组和集合的区别及定义方式

数组和集合的定义 一、数组 数组是java语言内置的数据类型&#xff0c;他是一个线性的序列&#xff0c;所有可以快速访问其他的元素&#xff0c;数组和其他语言不同&#xff0c;当你创建了一个数组时&#xff0c;他的容量是不变的&#xff0c;而且在生命周期也是不能改变的&a…

Java的数组与集合

Java的数组与集合 数组1.数组的概念2.数组的定义3.数组的初始化数组静态初始化数组动态初始化数组默认初始化值 4.数组元素访问5.数组的遍历6.数组的内存图JVM的内存图数组的内存图扩展 集合1.集合与数组的比较基本数据类型的包装类 2.集合中的成员方法 数组 1.数组的概念 数组…

数组与集合有什么不同之处

这个问题其实就是一个非常基础的面试题&#xff0c;一般面试官想了解你基础知识方面的掌握时&#xff0c;基本都会问这个问题&#xff0c;尤其是一些&#xff0c;计算机学子毕业之后&#xff0c;如果还是想要从事计算机技术相关的行业时&#xff0c;那么在面试的时候就需要做好…

kotlin数组和集合

一、Kotlin数组 1.对象数组 由Kotlin的main函数的写法&#xff0c;可以看出Kotlin中的对象数组写法与泛型的写法很像。 fun main(args: Array<String>){ } 声明对象数组的三种形式&#xff1a; (1)使用arrayOf函数和指定的数组元素创建数组 //Java写法: String[] p…

Java--数组和集合区别

一、数组 1、Java语言中的数组是一种引用数据类型&#xff1b;不属于基本数据类型 2、数组当中既可以存储“基本数据类型”的数据&#xff0c;也可以存储“引用数据类型”的数据&#xff08;数组既可以存储基本数据类型&#xff0c;又可以存储引用数据类型&#xff0c;基本数…

数组和集合的区别

一、数组声明了它容纳的元素的类型&#xff0c;而集合不声明。 二、数组是静态的&#xff0c;一个数组实例具有固定的大小&#xff0c;一旦创建了就无法改变容量了。而集合是可以动态扩展容量&#xff0c;可以根据需要动态改变大小&#xff0c;集合提供更多的成员方法&#xff…