改进 EFCore 查询以实现快速操作
Improve EFCore Query for fast operation
我读了一个sqlite数据库(数据库的大小大约是3MB,所以信息不多,每个table大概一两千行)然后从中提取信息,然后我将此信息添加到新数据库中。
整个操作大约需要40秒。
如何减少这个时间并尽快完成手术? (任务、并行、异步...)
我目前正在使用此代码:
await Task.Run(async () =>
{
var pkgs = new ManifestTable();
var mydb = new dbContext();
await mydb.Database.EnsureDeletedAsync();
await mydb.Database.EnsureCreatedAsync();
using (var msixDB = new MSIXContext())
{
foreach (var item in await msixDB.IdsMSIXTable.ToListAsync())
{
var rowId = item.rowid;
var manifests = await msixDB.Set<ManifestMSIXTable>().Where((e) => e.id == rowId).ToListAsync();
foreach (var manifest in manifests)
{
pkgs = new ManifestTable();
pkgs.PackageId = item.id;
var productMap = await msixDB.ProductCodesMapMSIXTable.FirstOrDefaultAsync((e) => e.manifest == manifest.rowid);
if (productMap != null)
{
var prdCode = await msixDB.ProductCodesMSIXTable.FirstOrDefaultAsync((e) => e.rowid == productMap.productcode);
if (prdCode != null)
{
pkgs.ProductCode = prdCode.productcode;
}
}
var publisherMap = await msixDB.Set<PublishersMapMSIXTable>().FirstOrDefaultAsync((e) => e.manifest == manifest.rowid);
if (publisherMap != null)
{
var publisher = await msixDB.PublishersMSIXTable.FirstOrDefaultAsync((e) => e.rowid == publisherMap.norm_publisher);
if (publisher != null)
{
pkgs.Publisher = publisher.norm_publisher;
}
}
var pathPart = manifest.pathpart;
var yml = await msixDB.PathPartsMSIXTable.FirstOrDefaultAsync((e) => e.rowid == pathPart);
if (yml != null)
{
pkgs.YamlName = yml.pathpart;
}
var version = await msixDB.VersionsMSIXTable.FirstOrDefaultAsync((e) => e.rowid == manifest.version);
if (version != null)
{
pkgs.Version = version.version;
}
await mydb.ManifestTable.AddAsync(pkgs);
}
}
await mydb.SaveChangesAsync();
}
});
在尝试并行处理等事情之前,您应该首先查看是否有任何算法改进。
你有两个嵌套循环,所以如果每个 table 都有几千行,内部循环体将 运行ning 在 10^6 的数量级上,并不可怕,但是公平的数额。
在内部循环中,您会 运行 一大堆 FirstOrDefaultAsync
语句。如果这些没有索引,则需要扫描所有行,这会很慢。因此,首先要确保您有适合所有 table 的索引。这样做是为了确保在恒定时间内搜索特定项目。
您似乎也在用相同的参数重复查找 PublishersMapMSIXTable
。避免不必要的重复操作应该是首先要解决的问题之一,因为它只是浪费周期。
如果整个操作 运行 在后台线程上进行,那么所有异步调用不太可能有多大帮助,它会节省一点内存,但会导致线程之间出现一些反弹。因此,如果重要的常规同步方法的性能可能会快一点。
关于性能,一如既往,衡量。一个好的性能分析器应该告诉你大部分时间花在了什么地方,如果你没有秒表,添加一些秒表也很容易。如果他们试图猜测缓慢的部分是什么,即使是非常有经验的程序员也可能是完全错误的。
将数据库视为对象存储是有史以来最糟糕的主意。您必须尽可能减少数据库往返。在您的情况下 - 只需一个请求。如果您不知道哪个部分慢,也不要玩 Task.Run、Parallel 等。在您的情况下 - 数据库往返。
var mydb = new dbContext();
await mydb.Database.EnsureDeletedAsync();
await mydb.Database.EnsureCreatedAsync();
using (var msixDB = new MSIXContext())
{
var query =
from item in msixDB.IdsMSIXTable
from manifest in msixDB.Set<ManifestMSIXTable>().Where(e => e.id == item.rowId)
from productMap in msixDB.ProductCodesMapMSIXTable.Where(e => e.manifest == manifest.rowid).Take(1).DefaultIfEmpty()
from prdCode in msixDB.ProductCodesMSIXTable.Where(e => e.rowid == productMap.productcode).Take(1).DefaultIfEmpty();
from publisherMap in msixDB.Set<PublishersMapMSIXTable>().Where(e => e.manifest == manifest.rowid).Take(1).DefaultIfEmpty()
from publisher in msixDB.PublishersMSIXTable.Where(e => e.rowid == publisherMap.norm_publisher).Take(1).DefaultIfEmpty()
from yml in msixDB.PathPartsMSIXTable.Where(e => e.rowid == manifest.pathpart).Take(1).DefaultIfEmpty()
from version in msixDB.VersionsMSIXTable.Where(e => e.rowid == manifest.version).Take(1).DefaultIfEmpty()
select new ManifestTable
{
PackageId = item.id,
ProductCode = prdCode.productcode,
Publisher = publisher.norm_publisher,
YamlName = yml.pathpart,
Version = version.version
};
mydb.ManifestTable.AddRange(await query.ToListAsync());
await mydb.SaveChangesAsync();
}
我读了一个sqlite数据库(数据库的大小大约是3MB,所以信息不多,每个table大概一两千行)然后从中提取信息,然后我将此信息添加到新数据库中。 整个操作大约需要40秒。
如何减少这个时间并尽快完成手术? (任务、并行、异步...)
我目前正在使用此代码:
await Task.Run(async () =>
{
var pkgs = new ManifestTable();
var mydb = new dbContext();
await mydb.Database.EnsureDeletedAsync();
await mydb.Database.EnsureCreatedAsync();
using (var msixDB = new MSIXContext())
{
foreach (var item in await msixDB.IdsMSIXTable.ToListAsync())
{
var rowId = item.rowid;
var manifests = await msixDB.Set<ManifestMSIXTable>().Where((e) => e.id == rowId).ToListAsync();
foreach (var manifest in manifests)
{
pkgs = new ManifestTable();
pkgs.PackageId = item.id;
var productMap = await msixDB.ProductCodesMapMSIXTable.FirstOrDefaultAsync((e) => e.manifest == manifest.rowid);
if (productMap != null)
{
var prdCode = await msixDB.ProductCodesMSIXTable.FirstOrDefaultAsync((e) => e.rowid == productMap.productcode);
if (prdCode != null)
{
pkgs.ProductCode = prdCode.productcode;
}
}
var publisherMap = await msixDB.Set<PublishersMapMSIXTable>().FirstOrDefaultAsync((e) => e.manifest == manifest.rowid);
if (publisherMap != null)
{
var publisher = await msixDB.PublishersMSIXTable.FirstOrDefaultAsync((e) => e.rowid == publisherMap.norm_publisher);
if (publisher != null)
{
pkgs.Publisher = publisher.norm_publisher;
}
}
var pathPart = manifest.pathpart;
var yml = await msixDB.PathPartsMSIXTable.FirstOrDefaultAsync((e) => e.rowid == pathPart);
if (yml != null)
{
pkgs.YamlName = yml.pathpart;
}
var version = await msixDB.VersionsMSIXTable.FirstOrDefaultAsync((e) => e.rowid == manifest.version);
if (version != null)
{
pkgs.Version = version.version;
}
await mydb.ManifestTable.AddAsync(pkgs);
}
}
await mydb.SaveChangesAsync();
}
});
在尝试并行处理等事情之前,您应该首先查看是否有任何算法改进。
你有两个嵌套循环,所以如果每个 table 都有几千行,内部循环体将 运行ning 在 10^6 的数量级上,并不可怕,但是公平的数额。
在内部循环中,您会 运行 一大堆 FirstOrDefaultAsync
语句。如果这些没有索引,则需要扫描所有行,这会很慢。因此,首先要确保您有适合所有 table 的索引。这样做是为了确保在恒定时间内搜索特定项目。
您似乎也在用相同的参数重复查找 PublishersMapMSIXTable
。避免不必要的重复操作应该是首先要解决的问题之一,因为它只是浪费周期。
如果整个操作 运行 在后台线程上进行,那么所有异步调用不太可能有多大帮助,它会节省一点内存,但会导致线程之间出现一些反弹。因此,如果重要的常规同步方法的性能可能会快一点。
关于性能,一如既往,衡量。一个好的性能分析器应该告诉你大部分时间花在了什么地方,如果你没有秒表,添加一些秒表也很容易。如果他们试图猜测缓慢的部分是什么,即使是非常有经验的程序员也可能是完全错误的。
将数据库视为对象存储是有史以来最糟糕的主意。您必须尽可能减少数据库往返。在您的情况下 - 只需一个请求。如果您不知道哪个部分慢,也不要玩 Task.Run、Parallel 等。在您的情况下 - 数据库往返。
var mydb = new dbContext();
await mydb.Database.EnsureDeletedAsync();
await mydb.Database.EnsureCreatedAsync();
using (var msixDB = new MSIXContext())
{
var query =
from item in msixDB.IdsMSIXTable
from manifest in msixDB.Set<ManifestMSIXTable>().Where(e => e.id == item.rowId)
from productMap in msixDB.ProductCodesMapMSIXTable.Where(e => e.manifest == manifest.rowid).Take(1).DefaultIfEmpty()
from prdCode in msixDB.ProductCodesMSIXTable.Where(e => e.rowid == productMap.productcode).Take(1).DefaultIfEmpty();
from publisherMap in msixDB.Set<PublishersMapMSIXTable>().Where(e => e.manifest == manifest.rowid).Take(1).DefaultIfEmpty()
from publisher in msixDB.PublishersMSIXTable.Where(e => e.rowid == publisherMap.norm_publisher).Take(1).DefaultIfEmpty()
from yml in msixDB.PathPartsMSIXTable.Where(e => e.rowid == manifest.pathpart).Take(1).DefaultIfEmpty()
from version in msixDB.VersionsMSIXTable.Where(e => e.rowid == manifest.version).Take(1).DefaultIfEmpty()
select new ManifestTable
{
PackageId = item.id,
ProductCode = prdCode.productcode,
Publisher = publisher.norm_publisher,
YamlName = yml.pathpart,
Version = version.version
};
mydb.ManifestTable.AddRange(await query.ToListAsync());
await mydb.SaveChangesAsync();
}