//从一个CSV文件中 获取 单独一张表数据 导入到数据库
//涉及到分库分表,8个库,每个库下有2000张表
//单个文件行超过100万条
string[] allLine = File.ReadAllLines(@"D:\External_1\20170702\000002_0.csv");
Parallel.For(1, 9, new ParallelOptions { MaxDegreeOfParallelism = 8 }, database =>
{
//获取每个库的所有表数据
string[] databaseData = allLine.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 / 2000 + 1 == database).ToArray();
for (int step = 0; step < 2000; step++)
{
int datatable = database * 2000 - 2000 + step;
//获取单独一张表的数据
string[] b = databaseData.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 == datatable).ToArray();
//写入数据库
}
});
你重复遍历大集合的次数太多了。
Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 这个操作你也重复了两次。
试试这样:
先定义准备好容器:
ConcurrentBag[] bags= new ConcurrentBag[16000]
可以用StreamReader 一条一条的读,返回一个IEnumerable<String>
然后再Parallel.For处理到bags
foreach bag in bags:
sqlbulkcopy bag
一些细节:
Convert.ToInt32(line.Substring(0, line.IndexOf(',')))
从左到有一个字符一个字符的读取line,
no += (char - '0') * 10
一直读到逗号.
计算出no之后
table = no % 16000
db = table / 2000 + 1
不知道你们基于什么考虑,把数据分到16000个表里面。
感觉数据在一起,需要的时候过滤更方便。 另外如果不需要join的话,用nosql来读写也是不错的选择。
File.ReadLines 就是返回IEnumerable, 一条一条读,边读边分类。
下面代码用的是单线程的,只是为了说明思路。
分类主要看CPU,多线程处理需要Concurrent集合。需要单线程的List实际测试一下CPU和效率
保存到DB的时候。IO操作,加上分库,分表了,多线程保存肯定快很多。
var lists = new List<string>[16000]; for (int i = 0; i < 16000; i++) { lists[i] = new List<string>(); } var lines = File.ReadLines(@"D:\External_1\20170702\000002_0.csv"); foreach (var line in lines) { var no = Convert.ToInt32(line.Substring(0, line.IndexOf(','))); var tableNo = no % 16000; lists[tableNo].Add(line); } for (int db = 0; db < 8; db++) { for (int table = 0; table < 2000; table++) { var tableNo = db * 2000 + table; var data = lists[tableNo]; //todo:Save } }
你的代码,在ReadAll这个地方应该是比较耗内存的,可以考虑用StreamReader,你之后的已经是并行代码了,可优化的方式不多,也许可以试试多线程会不会快些。
另外,https://www.codeproject.com/Articles/9258/A-Fast-CSV-Reader 可以看下,也许有帮助。
数据量大,再怎么优化这循环,也改变不了速度;建议从大的思路上解决;例如做一个单独的任务定时跑,每次添加几百条到数据库就sleep一秒, 并且可以在晚上休息的时候跑任务;
是的,这是个定时跑的服务 。功能把文件按16000张表分成对应的CSV文件,然后导入到库中
@夏一跳: 定时跑就不用考虑那么多了,一次少跑一些,再sleep一下
你的代码有些问题
1.并行度为什么要设为8?这个数量最好和核数量一致
2.在你并行的时候你是否发现中间那几个针对读取的数据做了多次遍历操作?这几次重复操作代价相当高,看大概结构可以省为一次,这块修改完后应该cpu就会掉下来。
1、并行度设置为8是根据机器配置来调的,一般配置成2,开多了,CPU就高了。
2、中间那段多次遍历,因为涉及到8个库,和每个库下的2000张表 对应,需要这样的遍历,也就是这个地方CPU高
@夏一跳: 你这是什么逻辑,又想马儿跑的快又想马儿不吃草?cpu高好啊,高了说明资源使用率高啊。
这句
string[] databaseData = allLine.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 / 2000 + 1 == database).ToArray();
你说是不是可以改为1次而不是并行次?
string[] b = databaseData.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 == datatable).ToArray();
这种虽然对上下文有依赖,这明显的可以每次遍历后去掉已经选过的数据啊。
说不好听点,不偷懒的话对整个数据做一次遍历就可以完成你整个功能了,你这上上下下遍历了多少次了?
@Daniel Cai:
string[] databaseData = allLine.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 / 2000 + 1 == database).ToArray();
这句是为了获取8个库中,每个库中所有表的数据,所以想8个库并行处理,改为1次的话,时间慢啊
string[] b = databaseData.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 == datatable).ToArray();
这句是为了获取每个库中 2000张表的数据,所以需要循环2000次
试过清掉遍历后的数据,但是速度上也没有提升
@夏一跳: 针对同样数据量运行一次时间慢,并行着就会快?
在一次遍历的时候你是不是可以直接确认哪个表哪个库?为什么还要额外再搞2k次循环?
你要想速度快改成生产消费模式,读一行就入队列,后面从队列中抽出来做后续处理。
另外说了,导数据这种东西又不需要很高的实时性,1百万条数据又不多,5分钟处理完和20分钟处理完难道有区别了?
@Daniel Cai: 一个文件,不是对一张表,每一行记录可能对应一张表,需要把文件数据分到8个库,16000张表中,所以每个库需要遍历2000次.
用户的投资分析数据,每月初提供给用户,不单单是一个文件需要导入,所以想更快点.
@夏一跳: 怎么还没明白?遍历一次是不是就可以通过你的hash确定是哪个库哪张表了?搞个对象记录下数据信息及库表信息塞到队列里面,后面直接从队列里面抽了做后续处理就完了啊。后面估计消费者速度会慢于生产者,这种你后面可以采用批量写入,比如满100条写一次(这块稍有点麻烦,需要在确认生产者无数据后将剩余数据进行最后一次写入)
前面读取文件没什么花头,无非就是采用流读取,读一行分析一行直接写入队列(队列需要无锁,由于数据量不大所以普通的concurrentqueue就可以了,也可以根据你库的数量做8个q,hash第一次确认是哪个q,然后hash第二次确认是哪个表(此步也可以由消费者完成),然后记录表偏移量到对象中放到q中)
你提到的所有功能在现在普通的开发机上应该是在1分钟内完成(很久前有过类似功能,但数据量在1.2亿,后面是mongo,每秒处理印象中在120-140K左右)
前些时候也在处理csv文件,用的mysql ef,是存到 一个表中的,可以参考下。
1、如果文件过大(这边是判断超过50MB,大约30万行),不要一次性读取文件到内存中,用流读取,读取到一万条存一次数据库
2、拼接sql的方式插入数据库比较快
其实测试下来看,一次性读文件到内存的时间很快的,慢就在文件数据按16000张表单独获取费时间。
按表把文件数据分开,是为了导入到数据库方便。我用了BulkInsert方式插入,速度不慢
可以在循环里面再开Task
减少CPU就算了,提高速度是可以的,你需要多久执行完
一个CSV文件,100万行数据,200M左右大小,需要3分钟执行完。需要处理400个文件
目前是用几台机器跑文件,总时间上可以缩短
前面那个Where,执行 100万/8线程*8=100万次
后面这个where
string[] b = databaseData.Where(line => Convert.ToInt32(line.Substring(0, line.IndexOf(','))) % 16000 == datatable).ToArray();
databaseData的数据量= 100万/8=12.5万
执行了 12.5万*2000次查找*线程数量8=2亿次
多线程+迭代