這個問題有很長的版本,並且是一個簡短的版本。C#,EF和LINQ:將大型(7Mb)記錄插入到SQL Server中很慢
短的版本:
爲什麼都是LINQ和EF在插入單個,大(7 MB)記錄到遠程SQL Server數據庫這麼慢?
而這裏的長版本(有關變通方法的一些信息,這可能是有用的其他讀者):
下面所有的示例代碼確實運行不錯,但因爲我的用戶是在歐洲而我們的數據中心總部位於美國,因此速度很慢。但是,如果我在美國的虛擬PC上運行相同的代碼,它會立即運行。 (並且,不幸的是,我的公司希望保留內部所有數據,所以我不能使用Azure,亞馬遜雲服務等)
我的很多公司應用都涉及從Excel讀取/寫入數據到SQL服務器,而且我們經常需要將Excel文件的原始副本保存在SQL Server表中。
這很簡單,只需從本地文件中讀取原始數據並將其保存到記錄中。
private int SaveFileToSQLServer(string filename)
{
// Read in an Excel file, and store it in a SQL Server [External_File] record.
//
// Returns the ID of the [External_File] record which was added.
//
DateTime lastModifed = System.IO.File.GetLastWriteTime(filename);
byte[] fileData = File.ReadAllBytes(filename);
// Create a new SQL Server database record, containing our file's raw data
// (Note: the table has an IDENTITY Primary-Key, so will generate a ExtFile_ID for us.)
External_File newFile = new External_File()
{
ExtFile_Filename = System.IO.Path.GetFileName(filename),
ExtFile_Data = fileData,
ExtFile_Last_Modified = lastModifed,
Update_By = "mike",
Update_Time = DateTime.UtcNow
};
dc.External_Files.InsertOnSubmit(newFile);
dc.SubmitChanges();
return newFile.ExtFile_ID;
}
是的,沒有什麼驚喜,它工作正常。
但是,我注意到,對於大型Excel文件(7-8Mb),插入一個(大!)記錄的代碼需要40-50秒才能運行。我把它放在一個後臺線程中,並且它一切正常,但是,當然,如果用戶退出我的應用程序,這個過程將被終止,這會導致問題。
作爲一個測試,我試着用代碼來做到這一點,以取代該功能:
- 將文件複製到SQL Server機器
- 調用的存儲過程來讀取原始數據上的共享目錄(blob)放入同一張表
使用此方法,整個過程只需要3-4秒。
如果你有興趣,這裏的存儲過程我用上傳文件(該文件必須存儲在SQL服務器本機上的文件夾中)到數據庫中的記錄:
CREATE PROCEDURE [dbo].[UploadFileToDatabase]
@LocalFilename nvarchar(400)
AS
BEGIN
-- By far, the quickest way to do this is to copy the file onto the SQL Server machine, then call this stored
-- procedure to read the raw data into a [External_File] record, and link it to the Pricing Account record.
--
-- EXEC [dbo].[UploadPricingToolFile] 'D:\ImportData\SomeExcelFile.xlsm'
--
-- Returns: -1 if something went wrong (eg file didn't exist) or the ID of our new [External_File] record
--
-- Note that the INSERT will go wrong, if the user doesn't have "bulkadmin" rights.
-- "You do not have permission to use the bulk load statement."
-- EXEC master..sp_addsrvrolemember @loginame = N'GPP_SRV', @rolename = N'bulkadmin'
--
SET NOCOUNT ON;
DECLARE
@filename nvarchar(300), -- eg "SomeFilename.xlsx" (without the path)
@SQL nvarchar(2000),
@New_ExtFile_ID int
-- Extract (just) the filename from our Path+Filename parameter
SET @filename = RIGHT(@LocalFilename,charindex('\',reverse(@LocalFilename))-1)
SET @SQL = 'INSERT INTO [External_File] ([ExtFile_Filename], [ExtFile_Data]) '
SET @SQL = @SQL + 'SELECT ''' + @Filename + ''', *
SET @SQL = @SQL + ' FROM OPENROWSET(BULK ''' + @LocalFilename +''', SINGLE_BLOB) rs'
PRINT convert(nvarchar, GetDate(), 108) + ' Running: ' + @SQL
BEGIN TRY
EXEC (@SQL)
SELECT @New_ExtFile_ID = @@IDENTITY
END TRY
BEGIN CATCH
PRINT convert(nvarchar, GetDate(), 108) + ' An exception occurred.'
SELECT -1
RETURN
END CATCH
PRINT convert(nvarchar, GetDate(), 108) + ' Finished.'
-- Return the ID of our new [External_File] record
SELECT @New_ExtFile_ID
END
的關鍵這段代碼,它建立了這樣一個SQL命令:
INSERT INTO [External_File] ([ExtFile_Filename], [ExtFile_Data])
SELECT 'SomeFilename.xlsm', * FROM OPENROWSET(BULK N'D:\ImportData\SomeExcelFile.xlsm', SINGLE_BLOB) rs
..和,如數據庫和文件上傳都在同一臺機器上,這種運行幾乎瞬間。
正如我所說的,總的來說,它需要3-4秒將文件複製到SQL Server計算機上的文件夾,並運行此存儲過程,相比之下,使用C#代碼與LINQ執行相同的操作需要40-50秒或EF。
導出從SQL Server blob數據到外部文件
而且,當然,同樣是在相反的方向實現。首先,我寫了一些C#/ LINQ代碼來加載一個(7Mb!)數據庫記錄並將其二進制數據寫入一個原始文件。這需要大約30-40秒的時間來運行。
但如果我導出SQL Server數據到一個文件(保存在SQL Server計算機上)第一..
EXEC master..xp_cmdshell 'BCP "select ef.ExtFile_Data FROM [External_File] ef where ExtFile_ID = 585" queryout "D:\ImportData\SomeExcelFile.xslx" -T -N'
...然後從SQL Server文件夾中的文件複製到用戶的文件夾,然後再一次,它在幾秒鐘內運行。
這是我的問題:爲什麼LINQ和EF在將大量記錄插入數據庫時如此糟糕?
我假設潛伏期(我們之間的距離,這裏是歐洲,還有我們在美國的數據中心)是導致延遲的主要原因,但是一個慢速標準文件複製可能會更快。
我錯過了什麼嗎?
很顯然,我發現walkarounds到這些問題,但它們涉及加在SQL Server計算機一些額外的權限,我們的SQL Server的機器和共享文件夾,我們的數據庫管理員真的不喜歡像「xp_cmdshell
授予的東西的權利」 ......
幾個月後...
我這周再次有同樣的問題,並試圖凱文·H公司建議,使用大容量插入插入大(6MB)記錄到SQL服務器。
使用批量插入,即使我們的數據中心距離6000英里,插入6Mb記錄也需要大約90秒。因此,故事的寓意:當插入非常大的數據庫記錄時,避免使用常規的SubmitChanges()
命令,並堅持使用批量插入。
不要將7mb文件插入到SQL服務器中...... –
嗯......你正在上傳7MB到遠程服務器。這可能是你的問題。 – FreeAsInBeer
是的,我知道包含7Mb數據的記錄遠非理想......但我的觀點是,SQL Server爲這種確切場景提供了blob數據類型,LINQ/EF很高興支持這種類型,但它們的性能很差。 。我的問題是:*爲什麼*表現如此糟糕..?爲什麼編寫單個記錄要比將文件複製到服務器要長20倍?爲什麼使用LINQ/EF會出現延遲這樣的問題? –