Postgresql快速写入\/读取大量数据(.net)

环境及测试

使用.net驱动npgsql连接post数据库。配置:win10 x64, i5-4590, 16G DDR3, SSD 850EVO.

postgresql 9.6.3,数据库与数据都安装在SSD上,默认配置,无扩展。

CREATE TABLE public.mesh
(x integer NOT NULL,y integer NOT NULL,z integer,  CONSTRAINT prim PRIMARY KEY (x, y)
)

1. 导入

使用数据备份,csv格式导入,文件位于机械硬盘上,480MB,数据量2500w+。

  • 使用COPY

    copy mesh from 'd:/user.csv' csv

    运行时间107s

  • 使用insert

    单连接,c# release any cpu 非调试模式。

class Program{    static void Main(string[] args)    {        var list = GetData("D:\\user.csv");TimeCalc.LogStartTime();        using (var sm = new SqlManipulation(@"Strings", SqlType.PostgresQL)){sm.Init();            foreach (var n in list){sm.ExcuteNonQuery($"insert into mesh(x,y,z) values({n.x},{n.y},{n.z})");}}TimeCalc.ShowTotalDuration();Console.ReadKey();}    static List<(int x, int y, int z)> GetData(string filepath){List<ValueTuple<int, int, int>> list = new List<(int, int, int)>();        foreach (var n in File.ReadLines(filepath)){            string[] x = n.Split(',');list.Add((Convert.ToInt32(x[0]), Convert.ToInt32(x[1]), Convert.ToInt32(x[2])));}        return list;}
}

Postgresql CPU占用率很低,但是跑了一年,程序依然不能结束,没有耐性了...,这么插入不行。

  • multiline insert

使用multiline插入,一条语句插入约100条数据。

var bag = GetData("D:\\user.csv");//使用时,直接执行stringbuilder的tostring方法。List<StringBuilder> listbuilder = new List<StringBuilder>();
StringBuilder sb = new StringBuilder();for (int i = 0; i < bag.Count; i++)
{    if (i % 100 == 0){sb = new StringBuilder();listbuilder.Add(sb);sb.Append("insert into mesh(x,y,z) values");sb.Append($"({bag[i].x}, {bag[i].y}, {bag[i].z})");}    elsesb.Append($",({bag[i].x}, {bag[i].y}, {bag[i].z})");
}

Postgresql CPU占用率差不多27%,磁盘写入大约45MB/S,感觉就是在干活,最后时间217.36s。
改为1000一行的话,CPU占用率提高,但是磁盘写入平均来看有所降低,最后时间160.58s.

  • prepare语法

prepare语法可以让postgresql提前规划sql,优化性能。

使用单行插入 CPU占用率不到25%,磁盘写入63MB/S左右,但是,使用单行插入的方式,效率没有改观,时间太长还是等不来结果。

使用多行插入 CPU占用率30%,磁盘写入50MB/S,最后结果163.02,最后的时候出了个异常,就是最后一组数据长度不满足条件,无伤大雅。

static void Main(string[] args){   
 var bag = GetData("D:\\user.csv");List<StringBuilder> listbuilder = new List<StringBuilder>();StringBuilder sb = new StringBuilder();  
   for (int i = 0; i < bag.Count; i++){        if (i % 1000 == 0){sb = new StringBuilder();listbuilder.Add(sb);          
     //sb.Append("insert into mesh(x,y,z) values");sb.Append($"{bag[i].x}, {bag[i].y}, {bag[i].z}");}        elsesb.Append($",{bag[i].x}, {bag[i].y}, {bag[i].z}");}StringBuilder sbp = new StringBuilder();sbp.Append("PREPARE insertplan (");  
       for (int i = 0; i < 1000; i++){sbp.Append("int,int,int,");}sbp.Remove(sbp.Length - 1, 1);sbp.Append(") AS INSERT INTO mesh(x, y, z) values");  
         for (int i = 0; i < 1000; i++){sbp.Append($"(${i*3 + 1},${i* 3 + 2},${i*3+ 3}),");}sbp.Remove(sbp.Length - 1, 1);TimeCalc.LogStartTime();  
          using (var sm = new SqlManipulation(@"string", SqlType.PostgresQL)){sm.Init();sm.ExcuteNonQuery(sbp.ToString());    
             foreach (var n in listbuilder){sm.ExcuteNonQuery($"EXECUTE insertplan({n.ToString()})");}}TimeCalc.ShowTotalDuration();Console.ReadKey(); }
  • 使用Transaction

    在前面的基础上,使用事务改造。每条语句插入1000条数据,每1000条作为一个事务,CPU 30%,磁盘34MB/S,耗时170.16s。
    改成100条一个事务,耗时167.78s。

  • 使用多线程

    还在前面的基础上,使用多线程,每个线程建立一个连接,一个连接处理100条sql语句,每条sql语句插入1000条数据,以此种方式进行导入。注意,连接字符串可以将maxpoolsize设置大一些,我机器上实测,不设置会报连接超时错误。

CPU占用率上到80%, 磁盘这里需要注意,由于生成了非常多个Postgresql server进程,不好统计,累积算上应该有小100MB/S,最终时间,98.18s。

使用TPL,由于Parallel.ForEach返回的结果没有检查,可能导致时间不是很准确(偏小)。

var lists = new List<List<string>>();
var listt = new List<string>();
for (int i = 0; i < listbuilder.Count; i++){  
 if (i % 1000 == 0){listt = new List<string>();lists.Add(listt);}listt.Add(listbuilder[i].ToString());} TimeCalc.LogStartTime();Parallel.ForEach(lists, (x) => {  
   using (var sm = new SqlManipulation(@";string;MaxPoolSize=1000;", SqlType.PostgresQL)){sm.Init();foreach (var n in x){sm.ExcuteNonQuery(n);}} });TimeCalc.ShowTotalDuration();
写入方式耗时(1000条/行)
COPY107s
insertN/A
多行insert160.58s
prepare多行insert163.02s
事务多行insert170.16s
多连接多行insert98.18s

2. 写入更新

数据实时更新,数量可能继续增长,使用简单的insert或者update是不行的,操作使用postgresql 9.5以后支持的新语法。

insert into mesh on conflict (x,y) 
do update set z = excluded.z

吐槽postgresql这么晚才支持on conflict,mysql早有了...

在表中既有数据2500w+的前提下,重复往数据库里面写这些数据。这里只做多行插入更新测试,其他的结果应该差不多。

普通多行插入,耗时272.15s。
多线程插入的情况,耗时362.26s,CPU占用率一度到了100%。猜测多连接的情况下,更新互锁导致性能下降。

3. 读取

  • Select方法

标准读取还是用select方法,ADO.NET直接读取。

使用adapter方式,耗时135.39s;使用dbreader方式,耗时71.62s。

  • Copy方法

postgresql的copy方法提供stdout binary方式,可以指定一条查询进行输出,耗时53.20s。

public List<(int x, int y, int z)> BulkIQueryNpg()
{List<(int, int, int)> dict = new List<(int, int, int)>();  
 using (var reader = ((NpgsqlConnection)_conn).BeginBinaryExport("COPY (select x,y,z from mesh) TO STDOUT (FORMAT BINARY)")){      
  while (reader.StartRow() != -1){          
   var x = reader.Read<int>(NpgsqlDbType.Integer);      
         var y = reader.Read<int>(NpgsqlDbType.Integer);      
       var z = reader.Read<int>(NpgsqlDbType.Integer);dict.Add((x, y, z));}}    return dict; }

结论

总结测试结果,对于较多数据的情况下,可以得出以下结论:

  • 向空数据表导入或者没有重复数据表的导入,优先使用COPY语句(为什么有这个前提详见P.S.);

  • 使用一条语句插入多条数据的方式能够大幅度改善插入性能,可以实验确定最优条数;

  • 使用transaction或者prepare插入,在本场景中优化效果不明显;

  • 使用多连接/多线程操作,速度上有优势,但是把握不好容易造成资源占用率过高,连接数太大也容易影响其他应用;

  • 写入更新是postgresql新特性,使用会造成一定的性能消耗(相对直接插入);

  • 读取数据时,使用COPY语句能够获得较好的性能;

  • ado.net dbreader对象由于不需要fill的过程,读取速度也较快(虽然赶不上COPY),也可优先考虑。

P.S.

  • 为什么不用mysql

没有最好的,只有最合适的,讲道理我也是挺喜欢用mysql的。使用postgresql的原因主要在于:

postgresql导入导出的sql指令“copy”直接支持Binary模式到stdin和stdout,如果程序想直接集成,那么用这个是比较方便的;相比较,mysql的sql语法(load data infile)并不支持到stdin或者stdout,导出可以通过mysqldump.exe实现,导入暂时没什么特别好的办法(mysqlimport或许可以)。

  • 相较于mysql缺点

postgresql使用copy导入的时候,如果目标表已经有数据,那么在有主键约束的表遇到错误时,COPY自动终止,而且可能导致不完全插入的情况,换言之,是不支持导入的过程进行update操作;mysql的load语法可以显式指定出错之后的动作(IGNORE/REPLACE),不会打断导入过程。

  • 其他

如果需要使用mysql从程序导入数据,可以考虑先通过程序导出到文件,然后借助文件进行导入,据说效率也要比insert高出不少。

原文地址:http://www.cnblogs.com/podolski/p/7152144.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/324453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mybatis更新Blob类型字段要用updateByPrimaryKeyWithBLOBs

转载自 mybatis更新Blob类型字段要用updateByPrimaryKeyWithBLOBs 不会报错也不会更新desc 业务desc数据库类型为Blob 查看源码 解决方案:

Ajax实现动态及时刷新表格数据

大家好&#xff0c;我是雄雄&#xff0c;今天分享的技术很简单&#xff0c;即ajax结合jdbc动态实现及时刷新表单数据。前言&#xff1a;相信大家在网上冲浪的时候&#xff0c;肯定会发现这样的场景&#xff0c;在实现某个查询功能时&#xff0c;下方表格中会显示需要展示的结果…

扩展entity framework core实现默认字符串长度,decimal精度,entity自动注册和配置

文章以efcore 2.0.0-preview2.测试验证通过。其他版本不保证使用&#xff0c;但是思路不会差太远。源代码,报道越短&#xff0c;事情越严重&#xff01;文章越短&#xff0c;内容越精悍&#xff01; 目标&#xff1a;1.实现entity的自动发现和mapper设置.2.默认字符串长度&…

上机不会做?在讲台上做做试试!

上周四班上到了sql语句的查询&#xff0c;正好临近周末&#xff0c;于是就在周末的时候布置了几个增删改查的案例让回家做做。今天随便找了几个人上黑板上做&#xff0c;本以为都没有问题了呢&#xff0c;结果做的一塌糊涂……惨&#xff0c;太惨了&#xff01;当时我就在想&am…

ASP.NET Core API 版本控制

几天前&#xff0c;我和我的朋友们使用 ASP.NET Core 开发了一个API &#xff0c;使用的是GET方式&#xff0c;将一些数据返回到客户端 APP。我们在前端进行了分页&#xff0c;意味着我们将所有数据发送给客户端&#xff0c;然后进行一些data.length操作&#xff0c;以获得item…

mybatis环境搭建步骤(含配置文件代码)

1.创建web项目2.将所需要的jar包放在项目内&#xff0c;并且build-path3.创建资源文件夹resources4.在资源文件夹中创建xml文件mybatis-config.xml,文件代码如下&#xff1a;<?xml version"1.0" encoding"UTF-8" ?> <!DOCTYPE configurationPUB…

多久没有给家里打过电话了?

你多久没有给家里打过电话了&#xff1f;对于我这种常年在外&#xff0c;且工作地距家直线距离都有数百公里的人来说&#xff0c;回家可是一种极大的奢侈啊。貌似自从在济南上班以来&#xff0c;平均每年也就有空回去两次&#xff0c;第一次一般都是有急事需要赶紧赶回去&#…

Feign数据压缩传输

没使用之前 使用 使用之后

漫画:删去k个数字后的最小值

转载自 漫画&#xff1a;删去k个数字后的最小值 我们来举一个栗子&#xff1a; 给定整数 541270936&#xff0c;要求删去一个数&#xff0c;让剩下的整数尽可能小。 此时&#xff0c;无论删除哪一个数字&#xff0c;最后的结果都是从9位整数变成8位整数。既然同样是8位整数&…

使用 InSpec 实现符合性即代码

法规符合性是每个企业必须面对的一个现实问题。同时&#xff0c;随着改变业界格局的新技术以及客户对数字服务的期望的出现&#xff0c;竞争压力也随之增加。各行业能否在快速交付新产品和服务的同时&#xff0c;仍然履行法规符合性义务&#xff1f; 回答是肯定的。解决方案就是…

计算机专业毕业后能做什么工作?

众所周知&#xff0c;目前比较火的专业之一莫过于计算机专业了。在这个互联网时代&#xff0c;越来越多的人选择去学习计算机专业&#xff0c;可是你知道计算机专业毕业后都有哪些岗位可选择吗&#xff1f;各个岗位的工作任务主要是什么&#xff1f;以下是对于计算机专业中各个…

什么是 binlog

转载自 什么是 binlog 引言 为什么写这篇文章? 大家当年在学MySQL的时候&#xff0c;为了能够迅速就业&#xff0c;一般是学习一下MySQL的基本语法&#xff0c;差不多就出山找工作了。水平稍微好一点的童鞋呢还会懂一点存储过程的编写&#xff0c;又或者是懂一点索引的创建…

[信息安全] 4.一次性密码 amp;amp;amp;amp; 身份认证三要素

在信息安全领域&#xff0c;一般把Cryptography称为密码&#xff0c;而把Password称为口令。日常用户的认知中&#xff0c;以及我们开发人员沟通过程中&#xff0c;绝大多数被称作密码的东西其实都是Password&#xff08;口令&#xff09;&#xff0c;而不是真正意义上的密码。…

干货!sqlserver数据库所有知识点总结整理,含代码(挺全的)

01T-SQL案例整理已知有一个表&#xff1a;该表的字段有&#xff1a;id,name,date,gradeid,email&#xff0c;表名为table_name,按要求实现下面内容。1.插入一条记录&#xff1a;insert into table_name values (1,刘世豪,2017-10-21,1,666qq.com)2.将学号是1的学生姓名修改成张…

深入源码分析Java线程池的实现原理

转载自 深入源码分析Java线程池的实现原理 程序的运行&#xff0c;其本质上&#xff0c;是对系统资源&#xff08;CPU、内存、磁盘、网络等等&#xff09;的使用。如何高效的使用这些资源是我们编程优化演进的一个方向。今天说的线程池就是一种对CPU利用的优化手段。 网上有…