我设计的应用程序,其中一个方面是,它应该是能够接收大量数据到SQL数据库。 我设计了数据库狭窄作为一个单一的表BIGINT身份,这样的事情之一:
CREATE TABLE MainTable
(
_id bigint IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
field1, field2, ...
)
我会忽略我怎么打算执行查询,因为它是不相关的我的问题。
我写了一个原型,其中中插入数据使用SqlBulkCopy的这个表。 它似乎在实验室中工作得很好。 我能够在3K〜记录/秒的速率插入数以千万计的记录(完整记录本身是相当大的,〜4K)。 由于在此表上唯一索引自动增量BIGINT,我还没有看到行显著量被推后也放缓。
考虑到实验室的SQL服务器是一个虚拟机相对较弱的配置(4GB内存,与其它的虚拟机磁盘sybsystem共享),我期待得到在物理机上显著更好的吞吐量,但它并没有发生,或者可以说,业绩增长可以忽略不计。 我可以,也许得到的物理机器上快25%的插入。 即使我配置3驱动RAID0,其进行比单个驱动器(用基准测试软件测量)快3倍,我没有改善。 基本上是:更快的驱动器子系统,专用物理CPU和RAM双几乎没有翻译成任何性能增益。
然后我重复使用Azure上的最大实例(8个核,16GB)的测试,并且我得到了相同的结果。 因此,增加更多的内核并没有改变插入速度。
在这个时候,我已经打得四处没有任何显著的性能增益以下软件的参数:
- 修改SqlBulkInsert.BatchSize参数
- 从多个线程同时插入,并调整线程#
- 使用上SqlBulkInsert表锁选项
- 通过使用共享存储器驱动从一个本地进程插入消除网络延迟
我想,以提高性能至少2-3次,和我原来的想法是,投入更多的硬件会得到吊环完成的,但到目前为止,没有。
因此,有人可以给我推荐:
- 什么资源可以在这里怀疑瓶颈? 如何确认?
- 有没有一种方法,我可以尝试得到可靠的可扩展的批量插入的改进考虑有一个单一的SQL服务器系统?
UPDATE我确信,负载的应用程序是没有问题的。 它创建一个临时队列记录在一个单独的线程,所以当有一个插入它是这样的(简化):
===>start logging time
int batchCount = (queue.Count - 1) / targetBatchSize + 1;
Enumerable.Range(0, batchCount).AsParallel().
WithDegreeOfParallelism(MAX_DEGREE_OF_PARALLELISM).ForAll(i =>
{
var batch = queue.Skip(i * targetBatchSize).Take(targetBatchSize);
var data = MYRECORDTYPE.MakeDataTable(batch);
var bcp = GetBulkCopy();
bcp.WriteToServer(data);
});
====> end loging time
定时记录,以及创建一个队列永远的部分以任何显著块
UPDATE2我已经实现收集在周期中的每个操作的持续时间以及布局如下:
-
queue.Skip().Take()
-可以忽略不计 -
MakeDataTable(batch)
- 10% -
GetBulkCopy()
-可以忽略不计 -
WriteToServer(data)
- 90%
UPDATE3我设计的SQL的标准版本,所以不能依靠分区,因为它只有在企业版本。 但我想的分区方案的变体:
- 创建文件组16(G0至G15),
- 由16个表,用于插入只(T0至T15),每个结合到其各基团。 表是没有索引可言,甚至没有聚集INT身份。
- 该插入穿过每个所有16个表的数据将周期线程。 这使得几乎每一个大容量插入操作使用它自己的表保证
这的确在BULK INSERT产量约20%的改善。 CPU核,LAN接口,驱动器I / O没有最大化,并在约25%的最大容量使用。
UPDATE4我认为现在是好得不能再好。 我是能推刀片使用下面的技术合理的速度:
- 每个批量插入进入它自己的表,然后结果合并到一个主
- 表重新创建新的,每批量插入,表锁使用
- 二手IDataReader的执行从这里 ,而不是数据表。
- 来自多个客户进行批量插入
- 每个客户端使用单独的千兆VLAN访问SQL
- 端进程访问主表使用NOLOCK选项
- 我研究sys.dm_os_wait_stats,并sys.dm_os_latch_stats消除争
我也很难在这一点上谁得到的回答问题信用来决定。 你们当中谁没有得到一个“回答”,我道歉,这是一个非常艰难的决定,我感谢大家。
UPDATE5:下列项目可以使用一些优化:
- 二手IDataReader的执行从这里 ,而不是数据表。
除非你有大量的CPU核心数的机器上运行您的程序,它可以使用一些重新分解。 由于它使用反射来生成get / set方法,变成对CPU的负载大。 如果性能是一个关键,它增添了不少的性能,当你手工编码的IDataReader,所以它被编译,而是采用反射