SQL Server에 200만 행 빠르게 삽입
텍스트 파일에서 약 200만 행을 삽입해야 합니다.
삽입으로 마스터 테이블을 작성해야 합니다.
SQL Server에 이러한 대규모 데이터 세트를 삽입하는 가장 빠르고 최선의 방법은 무엇입니까?
데이터셋에 있는 텍스트 파일의 데이터를 읽는 것이 좋다고 생각합니다.
SqlBulkCopy - C# App에서 SQL에 일괄 삽입을 시도합니다.
// connect to SQL using (SqlConnection connection = new SqlConnection(connString)) { // make sure to enable triggers // more on triggers in next post SqlBulkCopy bulkCopy = new SqlBulkCopy( connection, SqlBulkCopyOptions.TableLock | SqlBulkCopyOptions.FireTriggers | SqlBulkCopyOptions.UseInternalTransaction, null ); // set the destination table name bulkCopy.DestinationTableName = this.tableName; connection.Open(); // write the data in the "dataTable" bulkCopy.WriteToServer(dataTable); connection.Close(); } // reset this.dataTable.Clear();
또는
맨 위에서 1단계 실행 후
- DataSet에서 XML 생성
- XML을 데이터베이스에 전달하고 대량 삽입을 수행합니다.
자세한 내용은 C# DataTable 및 SQL Server OpenXML 함수를 사용한 데이터 일괄 삽입을 참조하십시오.
그러나 200만 장의 레코드로 테스트되지 않고 200만 장의 레코드를 로드하여 삽입해야 하므로 기계상의 메모리만 소비합니다.
수업과 함께 해 볼 수 있습니다.
다른 원본의 데이터를 사용하여 SQL 서버 테이블을 효율적으로 대량 로드할 수 있습니다.
사용법에 대한 멋진 블로그 투고가 있습니다.
SqlBulkCopy 솔루션:
StreamReader를 사용하여 텍스트 파일을 변환하고 처리했습니다.결과는 내 물건 목록이었다.
수강 이상의 클래스를 만들었습니다.Datatable
또는List<T>
및 버퍼 크기(CommitBatchSize
(두 번째 클래스의) 확장자를 사용하여 목록을 데이터 테이블로 변환합니다.
그것은 매우 빨리 작동한다.PC에서는 10초 이내에 1,000만 개 이상의 복잡한 레코드를 삽입할 수 있습니다.
클래스는 다음과 같습니다.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DAL
{
public class BulkUploadToSql<T>
{
public IList<T> InternalStore { get; set; }
public string TableName { get; set; }
public int CommitBatchSize { get; set; }=1000;
public string ConnectionString { get; set; }
public void Commit()
{
if (InternalStore.Count>0)
{
DataTable dt;
int numberOfPages = (InternalStore.Count / CommitBatchSize) + (InternalStore.Count % CommitBatchSize == 0 ? 0 : 1);
for (int pageIndex = 0; pageIndex < numberOfPages; pageIndex++)
{
dt= InternalStore.Skip(pageIndex * CommitBatchSize).Take(CommitBatchSize).ToDataTable();
BulkInsert(dt);
}
}
}
public void BulkInsert(DataTable dt)
{
using (SqlConnection connection = new SqlConnection(ConnectionString))
{
// make sure to enable triggers
// more on triggers in next post
SqlBulkCopy bulkCopy =
new SqlBulkCopy
(
connection,
SqlBulkCopyOptions.TableLock |
SqlBulkCopyOptions.FireTriggers |
SqlBulkCopyOptions.UseInternalTransaction,
null
);
// set the destination table name
bulkCopy.DestinationTableName = TableName;
connection.Open();
// write the data in the "dataTable"
bulkCopy.WriteToServer(dt);
connection.Close();
}
// reset
//this.dataTable.Clear();
}
}
public static class BulkUploadToSqlHelper
{
public static DataTable ToDataTable<T>(this IEnumerable<T> data)
{
PropertyDescriptorCollection properties =
TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
table.Rows.Add(row);
}
return table;
}
}
}
다음은 사용자 지정 개체 목록을 삽입하는 예입니다.List<PuckDetection>
(ListDetections
):
var objBulk = new BulkUploadToSql<PuckDetection>()
{
InternalStore = ListDetections,
TableName= "PuckDetections",
CommitBatchSize=1000,
ConnectionString="ENTER YOU CONNECTION STRING"
};
objBulk.Commit();
그BulkInsert
필요에 따라 클래스를 변경하여 열 매핑을 추가할 수 있습니다.예를 들어 Identity 키를 첫 번째 열로 지정합니다(데이터 테이블의 열 이름이 데이터베이스와 동일하다고 가정합니다).
//ADD COLUMN MAPPING
foreach (DataColumn col in dt.Columns)
{
bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
}
저는 bcp 유틸리티를 사용합니다. (Bulk Copy Program) 저는 매달 약 150만 개의 문자 기록을 로드합니다.각 텍스트 레코드의 너비는 800자입니다.내 서버에서는 150만 개의 텍스트 레코드를 SQL Server 테이블에 추가하는 데 약 30초가 걸립니다.
bcp 의 순서에 대해서는, http://msdn.microsoft.com/en-us/library/ms162802.aspx 를 참조해 주세요.
최근 이 시나리오(700만 행이 훨씬 넘는 경우)에서 한 번에 5,000개의 세그먼트에서 sqlcmd via powershell(생데이터를 SQL insert 문으로 해석한 후)을 사용하기 시작했습니다(SQL은 한 번에 700만 행을 처리할 수 없습니다.또한 5K로 분할하지 않으면 500,000 행을 처리할 수 없습니다).SQL Server 2012 Enterprise에서 새로운 sequence 명령을 활용해야 했기 때문에 각 5K 스크립트를 차례로 실행할 수 있습니다.시퀀스 명령으로 700만 행의 데이터를 빠르고 효율적으로 삽입할 수 있는 프로그램을 찾을 수 없었습니다.
둘째, 한 번에 100만 행 이상의 데이터를 삽입할 때 주의해야 할 사항 중 하나는 삽입 프로세스 중 CPU와 메모리 소비량(대부분 메모리)입니다.SQL은 해당 프로세스를 해제하지 않고 이 크기의 작업으로 메모리/CPU를 소비합니다.서버에 충분한 처리 능력이나 메모리가 없는 경우는, 단시간에 크래쉬 할 수 있습니다(어렵게 알았습니다).메모리 소비량이 70~75%를 넘는 경우 서버를 재부팅하면 프로세스가 정상으로 돌아갑니다.
실제로 최종 실행 계획을 세우기 전에 서버의 제한(CPU/메모리 리소스가 한정되어 있는 경우)을 확인하기 위해 수많은 시행착오 테스트를 수행해야 했습니다.실제 가동 환경에 도입하기 전에 테스트 환경에서 동일한 작업을 수행하는 것이 좋습니다.
이 방법으로 시도했더니 데이터베이스 삽입 실행 시간이 대폭 단축되었습니다.
List<string> toinsert = new List<string>();
StringBuilder insertCmd = new StringBuilder("INSERT INTO tabblename (col1, col2, col3) VALUES ");
foreach (var row in rows)
{
// the point here is to keep values quoted and avoid SQL injection
var first = row.First.Replace("'", "''")
var second = row.Second.Replace("'", "''")
var third = row.Third.Replace("'", "''")
toinsert.Add(string.Format("( '{0}', '{1}', '{2}' )", first, second, third));
}
if (toinsert.Count != 0)
{
insertCmd.Append(string.Join(",", toinsert));
insertCmd.Append(";");
}
using (MySqlCommand myCmd = new MySqlCommand(insertCmd.ToString(), SQLconnectionObject))
{
myCmd.CommandType = CommandType.Text;
myCmd.ExecuteNonQuery();
}
* SQL connection Object를 작성한 위치에 SQL connection Object를 만듭니다.
ADO, 엔티티 및 Dapper와 연동해야 하는 솔루션의 문제에 직면했습니다.따라서 이 lib는 다음과 같은 형태로 배치가 생성됩니다.
IEnumerable<(string SqlQuery, IEnumerable<SqlParameter> SqlParameters)>
IEnumerable<(string SqlQuery, DynamicParameters DapperDynamicParameters)>
이 링크에는 순서가 포함되어 있습니다.SQL Injection에 대해 안전합니다.연결 대신 매개 변수를 사용하기 때문에 필요에 따라 옵션 매개 변수를 사용하여 ID 삽입을 ON으로 설정할 수도 있습니다.
ADO에서의 사용.네트워크:
using MsSqlHelpers;
// ...
var mapper = new MapperBuilder<Person>()
.SetTableName("People")
.AddMapping(person => person.FirstName, columnName: "Name")
.AddMapping(person => person.LastName, columnName: "Surename")
.AddMapping(person => person.DateOfBirth, columnName: "Birthday")
.Build();
var people = new List<Person>()
{
new Person()
{
FirstName = "John",
LastName = "Lennon",
DateOfBirth = new DateTime(1940, 10, 9)
},
new Person()
{
FirstName = "Paul",
LastName = "McCartney",
DateOfBirth = new DateTime(1942, 6, 18)
},
};
var connectionString = "Server=SERVER_ADDRESS;Database=DATABASE_NAME;User Id=USERNAME;Password=PASSWORD;";
var sqlQueriesAndParameters = new MsSqlQueryGenerator()
.GenerateParametrizedBulkInserts(mapper, people);
using (var sqlConnection = new SqlConnection(connectionString))
{
sqlConnection.Open();
// Default batch size: 1000 rows or (2100-1) parameters per insert.
foreach (var (SqlQuery, SqlParameters) in sqlQueriesAndParameters)
{
using (SqlCommand sqlCommand = new SqlCommand(SqlQuery, sqlConnection))
{
sqlCommand.Parameters.AddRange(SqlParameters.ToArray());
sqlCommand.ExecuteNonQuery();
}
}
}
Dapper에서의 사용:
using MsSqlHelpers;
// ...
var mapper = new MapperBuilder<Person>()
.SetTableName("People")
.AddMapping(person => person.FirstName, columnName: "Name")
.AddMapping(person => person.LastName, columnName: "Surename")
.AddMapping(person => person.DateOfBirth, columnName: "Birthday")
.Build();
var people = new List<Person>()
{
new Person()
{
FirstName = "John",
LastName = "Lennon",
DateOfBirth = new DateTime(1940, 10, 9)
},
new Person()
{
FirstName = "Paul",
LastName = "McCartney",
DateOfBirth = new DateTime(1942, 6, 18)
},
};
var connectionString = "Server=SERVER_ADDRESS;Database=DATABASE_NAME;User Id=USERNAME;Password=PASSWORD;";
var sqlQueriesAndDapperParameters = new MsSqlQueryGenerator()
.GenerateDapperParametrizedBulkInserts(mapper, people);
using (var sqlConnection = new SqlConnection(connectionString))
{
// Default batch size: 1000 rows or (2100-1) parameters per insert.
foreach (var (SqlQuery, DapperDynamicParameters) in sqlQueriesAndDapperParameters)
{
sqlConnection.Execute(SqlQuery, DapperDynamicParameters);
}
}
엔티티 프레임워크에서의 사용:
using MsSqlHelpers;
// ...
var mapper = new MapperBuilder<Person>()
.SetTableName("People")
.AddMapping(person => person.FirstName, columnName: "Name")
.AddMapping(person => person.LastName, columnName: "Surename")
.AddMapping(person => person.DateOfBirth, columnName: "Birthday")
.Build();
var people = new List<Person>()
{
new Person()
{
FirstName = "John",
LastName = "Lennon",
DateOfBirth = new DateTime(1940, 10, 9)
},
new Person()
{
FirstName = "Paul",
LastName = "McCartney",
DateOfBirth = new DateTime(1942, 6, 18)
},
};
var sqlQueriesAndParameters = new MsSqlQueryGenerator()
.GenerateParametrizedBulkInserts(mapper, people);
// Default batch size: 1000 rows or (2100-1) parameters per insert.
foreach (var (SqlQuery, SqlParameters) in sqlQueriesAndParameters)
{
_context.Database.ExecuteSqlRaw(SqlQuery, SqlParameters);
// Depracated but still works: _context.Database.ExecuteSqlCommand(SqlQuery, SqlParameters);
}
또한 텍스트 데이터가 Json 형식인 경우 SQL Server에서 대량 삽입에 OPENJSON 메서드를 사용할 수 있습니다.로컬 머신으로 테스트한 결과 51초 만에 100만 장의 레코드를 삽입할 수 있었습니다.
저장 프로시저는 다음과 같습니다.
CREATE PROCEDURE sp_upsert_play_user_details1
(
@array VARCHAR(MAX)
)
AS
BEGIN
BEGIN TRY
BEGIN TRANSACTION
INSERT INTO tbl_play_user_details
(vc_first_name, vc_last_name, vc_full_name, vc_gender, vc_phone_number, vc_email, vc_pet, vc_vehicle_model, vc_vehicle_no, int_created_on, int_created_by)
SELECT firstName, lastName, fullName, gender, phoneNumber, email, pet, vehicle, vehicleNumber, GETDATE(), createdBy FROM OPENJSON(@array)
WITH ( firstName VARCHAR(100),
lastName VARCHAR(100),
fullName VARCHAR(100),
gender VARCHAR(100),
phoneNumber VARCHAR(100),
email VARCHAR(100),
pet VARCHAR(100),
vehicle VARCHAR(100),
vehicleNumber VARCHAR(100),
createdBy int);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
DECLARE @ErrorMessage NVARCHAR(4000)=ERROR_MESSAGE()+' Please verify "'+ERROR_PROCEDURE()+'" stored procedure at the line number '+CONVERT(NVARCHAR(20),ERROR_LINE() )+ '.';
DECLARE @ErrorSeverity INT = ERROR_SEVERITY();
DECLARE @ErrorState INT=ERROR_STATE();
RAISERROR (@ErrorMessage,@ErrorSeverity,@ErrorState)
END CATCH
END
GO
테스트용 샘플 JSON:
DECLARE @array VARCHAR(MAX);
SET @array = '[{
"firstName": "Winston",
"lastName": "Lemke",
"fullName": "Winston Lemke",
"gender": "Male",
"phoneNumber": "466.780.4652 x268",
"email": "Josefa89@yahoo.com",
"pet": "Villanuco de Las Encartaciones",
"vehicle": "Mazda Escalade",
"vehicleNumber": "8CP7UC1N83MY25770",
"createdBy": 1
},
{
"firstName": "Finn",
"lastName": "Bartoletti",
"fullName": "Finn Bartoletti",
"gender": "Female",
"phoneNumber": "1-931-498-0214 x454",
"email": "Clair.Rodriguez@hotmail.com",
"pet": "Bouvier des Flandres",
"vehicle": "Tesla Ranchero",
"vehicleNumber": "MG1XVY29D0M798471",
"createdBy": 1
}]';
EXEC sp_upsert_play_user_details1 @array;
C#에서는 로컬파일의 데이터를 해석하여 스트링을 스토어드 프로시저에 전달했습니다.
string text = System.IO.File.ReadAllText(@"C:\Users\krish\OneDrive\Desktop\object.json");
_playdb.CommandTimeout = 3000;
_playdb.sp_upsert_play_user_details1(text);
위에서 설명한 바와 같이 1밀리 레코드를 삽입하는 데 51초밖에 걸리지 않았으며, 더 빠른 서버/워크 머신에서는 훨씬 더 빠를 수 있습니다.
언급URL : https://stackoverflow.com/questions/13722014/insert-2-million-rows-into-sql-server-quickly
'programing' 카테고리의 다른 글
SQL Server Management Studio 대체 기능으로 테이블 참조/편집 및 쿼리 실행 가능 (0) | 2023.04.20 |
---|---|
PowerShell 루프에서 현재 항목의 인덱스 가져오기 (0) | 2023.04.20 |
Excel VBA를 사용하여 명령 프롬프트에서 명령 실행 (0) | 2023.04.15 |
pod install -pod: pod: 명령을 찾을 수 없습니다. (0) | 2023.04.15 |
기본적으로 라디오 버튼을 선택하려면 어떻게 해야 합니까? (0) | 2023.04.15 |