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

    开发中经常遇到批量插入数据的需求,为了提高开发效率大多会使用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);
                    //时间或整形不能为null
                    if (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;
                }

                //被标记不与数据库映射的字段SugarColumn
                SugarColumn[] 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、平时要多自己写实例测试才能得到真实的结果,不能只看官网的宣传。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,您需要安装并配置Flink和Kafka,并且在Oracle数据库中已经存在要写入的表。 其次,您需要在pom.xml文件中添加Flink和Kafka的依赖以及Oracle数据库的驱动。 示例代码: ``` <dependency> <groupId>org.apache.flink</groupId> <artifactId>flink-connector-kafka_2.11</artifactId> <version>1.11.2</version> </dependency> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> ``` 最后,您可以使用以下代码来实现将Kafka中的数据写入Oracle数据库中: ``` StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); Properties properties = new Properties(); properties.setProperty("bootstrap.servers", "localhost:9092"); properties.setProperty("group.id", "test"); DataStream<String> stream = env.addSource(new FlinkKafkaConsumer011<>("topic", new SimpleStringSchema(), properties)); stream.map(new MapFunction<String, Tuple2<String, String>>() { @Override public Tuple2<String, String> map(String value) throws Exception { // 将Kafka中的数据进行处理,返回Tuple2<String, String>类型的数据 return new Tuple2<>(value.split(",")[0], value.split(",")[1]); } }).addSink(new OracleSink()); env.execute("Flink Oracle Sink"); ``` 这里的OracleSink是自定义的Sink类,需要实现Flink的SinkFunction接口,并重写open()、close()和invoke()方法,在open()方法中建立与Oracle数据库的连接,在close()方法中关闭连接,在invoke()方法中实现将数据写入Oracle数据库中的逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值