Laut Wikipedia ist die Masseneinfügung ein Prozess oder eine Methode, die von einem Datenbankverwaltungssystem bereitgestellt wird, um mehrere Datenzeilen in eine Datenbanktabelle zu laden. Wenn wir diese Erklärung an die BULK INSERT-Anweisung anpassen, ermöglicht die Masseneinfügung den Import externer Datendateien in SQL Server.
Angenommen, unsere Organisation hat eine CSV-Datei mit 1.500.000 Zeilen, und wir möchten sie in eine bestimmte Tabelle in SQL Server importieren, um die BULK INSERT-Anweisung in SQL Server zu verwenden. Wir können mehrere Methoden finden, um diese Aufgabe zu bewältigen. Es könnte BCP (b ulk c opy p Programm), SQL Server Import/Export-Assistent oder SQL Server Integration Service-Paket. Die BULK INSERT-Anweisung ist jedoch viel schneller und wirksamer. Ein weiterer Vorteil ist, dass es mehrere Parameter bietet, die dabei helfen, die Einstellungen für den Massenbeilagenprozess zu bestimmen.
Beginnen wir mit einem einfachen Beispiel. Dann werden wir anspruchsvollere Szenarien durchgehen.
Vorbereitung
Zunächst benötigen wir eine Beispiel-CSV-Datei. Wir laden eine Beispiel-CSV-Datei von der E for Excel-Website herunter (eine Sammlung von Beispiel-CSV-Dateien mit einer anderen Zeilennummer). Hier werden wir 1.500.000 Verkaufsdatensätze verwenden.
Laden Sie eine ZIP-Datei herunter, entpacken Sie sie, um eine CSV-Datei zu erhalten, und legen Sie sie auf Ihrem lokalen Laufwerk ab.
CSV-Datei in SQL Server-Tabelle importieren
Wir importieren unsere CSV-Datei in der einfachsten Form in die Zieltabelle. Ich habe meine Beispiel-CSV-Datei auf Laufwerk C:abgelegt. Jetzt erstellen wir eine Tabelle, um die CSV-Dateidaten darin zu importieren:
DROP TABLE IF EXISTS Sales CREATE TABLE [dbo].[Sales]( [Region] [varchar](50) , [Country] [varchar](50) , [ItemType] [varchar](50) NULL, [SalesChannel] [varchar](50) NULL, [OrderPriority] [varchar](50) NULL, [OrderDate] datetime, [OrderID] bigint NULL, [ShipDate] datetime, [UnitsSold] float, [UnitPrice] float, [UnitCost] float, [TotalRevenue] float, [TotalCost] float, [TotalProfit] float )
Die folgende BULK INSERT-Anweisung importiert die CSV-Datei in die Sales-Tabelle:
BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' );
Sie haben wahrscheinlich die spezifischen Parameter der obigen Bulk-Insert-Anweisung bemerkt. Lassen Sie uns sie klären:
- ERSTMALIG gibt den Startpunkt der Einfügeanweisung an. Im folgenden Beispiel möchten wir Spaltenüberschriften überspringen, also setzen wir diesen Parameter auf 2.
- FIELDTERMINATOR definiert das Zeichen, das Felder voneinander trennt. SQL Server erkennt jedes Feld auf diese Weise.
- ROWTERMINATOR unterscheidet sich nicht wesentlich von FIELDTERMINATOR. Es definiert das Trennzeichen von Zeilen.
In der Beispiel-CSV-Datei ist FIELDTERMINATOR sehr deutlich und es ist ein Komma (,). Um diesen Parameter zu erkennen, öffnen Sie die CSV-Datei in Notepad++ und navigieren Sie zu View -> Show Symbol -> Show All Charters. Die CRLF-Zeichen befinden sich am Ende jedes Felds.
CR =Wagenrücklauf und LF =Zeilenvorschub. Sie werden verwendet, um einen Zeilenumbruch in einer Textdatei zu markieren. Der Indikator ist „\n“ in der Bulk-Insert-Anweisung.
Eine andere Möglichkeit zum Importieren einer CSV-Datei in eine Tabelle mit Masseneinfügung ist die Verwendung des FORMAT-Parameters. Beachten Sie, dass dieser Parameter nur in SQL Server 2017 und späteren Versionen verfügbar ist.
BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FORMAT='CSV' , FIRSTROW = 2);
Das war das einfachste Szenario, bei dem die Zieltabelle und die CSV-Datei die gleiche Anzahl von Spalten haben. Wenn die Zieltabelle jedoch mehr Spalten hat, dann ist die CSV-Datei typisch. Lassen Sie uns darüber nachdenken.
Wir fügen der Sales-Tabelle einen Primärschlüssel hinzu, um die Gleichheitsspaltenzuordnungen aufzuheben. Wir erstellen die Sales-Tabelle mit einem Primärschlüssel und importieren die CSV-Datei über den Bulk-Insert-Befehl.
DROP TABLE IF EXISTS Sales CREATE TABLE [dbo].[Sales]( Id INT PRIMARY KEY IDENTITY (1,1), [Region] [varchar](50) , [Country] [varchar](50) , [ItemType] [varchar](50) NULL, [SalesChannel] [varchar](50) NULL, [OrderPriority] [varchar](50) NULL, [OrderDate] datetime, [OrderID] bigint NULL, [ShipDate] datetime, [UnitsSold] float, [UnitPrice] float, [UnitCost] float, [TotalRevenue] float, [TotalCost] float, [TotalProfit] float ) BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' );
Aber es erzeugt einen Fehler:
Um den Fehler zu umgehen, erstellen wir eine Ansicht der Sales-Tabelle mit Zuordnungsspalten zur CSV-Datei. Dann importieren wir die CSV-Daten über diese Ansicht in die Sales-Tabelle:
DROP VIEW IF EXISTS VSales GO CREATE VIEW VSales AS SELECT Region , Country , ItemType , SalesChannel , OrderPriority , OrderDate , OrderID , ShipDate , UnitsSold , UnitPrice , UnitCost , TotalRevenue, TotalCost, TotalProfit from Sales GO BULK INSERT VSales FROM 'C:\1500000 Sales Records.csv' WITH ( FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' );
Trennen und laden Sie eine große CSV-Datei in eine kleine Stapelgröße
SQL Server erwirbt während des Masseneinfügevorgangs eine Sperre für die Zieltabelle. Wenn Sie den BATCHSIZE-Parameter nicht festlegen, öffnet SQL Server standardmäßig eine Transaktion und fügt die gesamten CSV-Daten darin ein. Mit diesem Parameter teilt SQL Server die CSV-Daten entsprechend dem Parameterwert auf.
Lassen Sie uns die gesamten CSV-Daten in mehrere Sätze von jeweils 300.000 Zeilen aufteilen.
DROP TABLE IF EXISTS Sales CREATE TABLE [dbo].[Sales]( [Region] [varchar](50) , [Country] [varchar](50) , [ItemType] [varchar](50) NULL, [SalesChannel] [varchar](50) NULL, [OrderPriority] [varchar](50) NULL, [OrderDate] datetime, [OrderID] bigint NULL, [ShipDate] datetime, [UnitsSold] float, [UnitPrice] float, [UnitCost] float, [TotalRevenue] float, [TotalCost] float, [TotalProfit] float ) BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' , batchsize=300000 );
Die Daten werden in Teilen fünfmal importiert.
- Wenn Ihre Bulk-Insert-Anweisung den BATCHSIZE-Parameter nicht enthält, tritt ein Fehler auf und SQL Server setzt den gesamten Bulk-Insert-Prozess zurück.
- Wenn dieser Parameter auf Bulk-Insert-Anweisung gesetzt ist, setzt SQL Server nur den Teil zurück, in dem der Fehler aufgetreten ist.
Es gibt keinen optimalen oder besten Wert für diesen Parameter, da sich sein Wert entsprechend den Anforderungen Ihres Datenbanksystems ändern kann.
Stellen Sie das Verhalten bei Fehlern ein
Wenn in einigen Massenkopier-Szenarien ein Fehler auftritt, können wir den Massenkopiervorgang entweder abbrechen oder fortsetzen. Mit dem Parameter MAXERRORS können wir die maximale Anzahl von Fehlern angeben. Wenn der Masseneinfügungsprozess diesen maximalen Fehlerwert erreicht, wird der Massenimportvorgang abgebrochen und zurückgesetzt. Der Standardwert für diesen Parameter ist 10.
Beispielsweise haben wir beschädigte Datentypen in 3 Zeilen der CSV-Datei. Der Parameter MAXERRORS wird auf 2 gesetzt.
DROP TABLE IF EXISTS Sales CREATE TABLE [dbo].[Sales]( [Region] [varchar](50) , [Country] [varchar](50) , [ItemType] [varchar](50) NULL, [SalesChannel] [varchar](50) NULL, [OrderPriority] [varchar](50) NULL, [Order Date] datetime, [OrderID] bigint NULL, [ShipDate] datetime, [UnitsSold] float, [UnitPrice] float, [UnitCost] float, [TotalRevenue] float, [TotalCost] float, [TotalProfit] float ) BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' , MAXERRORS=2);
Der gesamte Bulk-Insert-Vorgang wird abgebrochen, da mehr Fehler vorliegen als der Parameterwert MAXERRORS.
Wenn wir den Parameter MAXERRORS auf 4 ändern, überspringt die Masseneinfügeanweisung diese Zeilen mit Fehlern und fügt Zeilen mit korrekter Datenstruktur ein. Der Bulk-Insert-Vorgang ist abgeschlossen.
DROP TABLE IF EXISTS Sales CREATE TABLE [dbo].[Sales]( [Region] [varchar](50) , [Country] [varchar](50) , [ItemType] [varchar](50) NULL, [SalesChannel] [varchar](50) NULL, [OrderPriority] [varchar](50) NULL, [Order Date] datetime, [OrderID] bigint NULL, [ShipDate] datetime, [UnitsSold] float, [UnitPrice] float, [UnitCost] float, [TotalRevenue] float, [TotalCost] float, [TotalProfit] float ) BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' , MAXERRORS=4); SELECT COUNT(*) AS [NumberofImportedRow] FROM Sales
Wenn wir sowohl BATCHSIZE als auch MAXERRORS gleichzeitig verwenden, bricht der Massenkopiervorgang nicht den gesamten Einfügevorgang ab. Es wird nur der geteilte Teil gelöscht.
DROP TABLE IF EXISTS Sales CREATE TABLE [dbo].[Sales]( [Region] [varchar](50) , [Country] [varchar](50) , [ItemType] [varchar](50) NULL, [SalesChannel] [varchar](50) NULL, [OrderPriority] [varchar](50) NULL, [Order Date] datetime, [OrderID] bigint NULL, [ShipDate] datetime, [UnitsSold] float, [UnitPrice] float, [UnitCost] float, [TotalRevenue] float, [TotalCost] float, [TotalProfit] float ) BULK INSERT Sales FROM 'C:\1500000 Sales Records.csv' WITH (FIRSTROW = 2, FIELDTERMINATOR = ',', ROWTERMINATOR='\n' , MAXERRORS=2, BATCHSIZE=750000); GO SELECT COUNT(*) AS [NumberofImportedRow] FROM Sales
Sehen Sie sich das folgende Bild an, das das Ergebnis der Skriptausführung zeigt:
Weitere Optionen des Masseninsertionsprozesses
FIRE_TRIGGERS – Trigger in der Zieltabelle während des Masseneinfügungsvorgangs aktivieren
Standardmäßig werden während des Masseneinfügungsprozesses die in der Zieltabelle angegebenen Einfügungsauslöser nicht ausgelöst. Dennoch möchten wir sie in einigen Situationen möglicherweise aktivieren.
Die Lösung verwendet die Option FIRE_TRIGGERS in Masseneinfügeanweisungen. Beachten Sie jedoch, dass dies die Leistung des Bulk-Insert-Vorgangs beeinträchtigen und verringern kann. Dies liegt daran, dass Trigger/Trigger separate Operationen in der Datenbank ausführen können.
Zuerst setzen wir den FIRE_TRIGGERS-Parameter nicht, und der Bulk-Insert-Prozess löst den Insert-Trigger nicht aus. Siehe das folgende T-SQL-Skript:
DROP TABLE IF EXISTS Sales
CREATE TABLE [dbo].[Sales](
[Region] [varchar](50) ,
[Country] [varchar](50) ,
[ItemType] [varchar](50) NULL,
[SalesChannel] [varchar](50) NULL,
[OrderPriority] [varchar](50) NULL,
[OrderDate] datetime,
[OrderID] bigint NULL,
[ShipDate] datetime,
[UnitsSold] float,
[UnitPrice] float,
[UnitCost] float,
[TotalRevenue] float,
[TotalCost] float,
[TotalProfit] float
)
DROP TABLE IF EXISTS SalesLog
CREATE TABLE SalesLog (OrderIDLog bigint)
GO
CREATE TRIGGER OrderLogIns ON Sales
FOR INSERT
AS
BEGIN
SET NOCOUNT ON
INSERT INTO SalesLog
SELECT OrderId from inserted
end
GO
BULK INSERT Sales
FROM 'C:\1500000 Sales Records.csv'
WITH (FIRSTROW = 2,
FIELDTERMINATOR = ',',
ROWTERMINATOR='\n'
);
SELECT Count(*) FROM SalesLog
Wenn dieses Skript ausgeführt wird, wird der Insert-Trigger nicht ausgelöst, da die Option FIRE_TRIGGERS nicht festgelegt ist.
Fügen wir nun die Option FIRE_TRIGGERS zur Bulk-Insert-Anweisung hinzu:
BULK INSERT Sales
FROM 'C:\1500000 Sales Records.csv'
WITH (FIRSTROW = 2,
FIELDTERMINATOR = ',',
ROWTERMINATOR='\n',
FIRE_TRIGGERS);
GO
SELECT Count(*) as [NumberOfRowsinTriggerTable] FROM SalesLog
CHECK_CONSTRAINTS – Aktivieren Sie eine Check-Einschränkung während des Masseneinfügungsvorgangs
Check Constraints ermöglichen es uns, die Datenintegrität in SQL Server-Tabellen zu erzwingen. Zweck des Constraints ist es, eingefügte, aktualisierte oder gelöschte Werte gemäß ihrer Syntaxvorschrift zu prüfen. Beispielsweise sorgt die NOT NULL-Einschränkung dafür, dass der NULL-Wert eine angegebene Spalte nicht ändern kann.
Hier konzentrieren wir uns auf Beschränkungen und Masseneinfügungsinteraktionen. Standardmäßig werden während des Masseneinfügungsprozesses alle Überprüfungs- und Fremdschlüsseleinschränkungen ignoriert. Aber es gibt einige Ausnahmen.
Laut Microsoft werden „UNIQUE- und PRIMARY KEY-Einschränkungen immer erzwungen. Beim Importieren in eine Zeichenspalte, für die die Einschränkung NOT NULL definiert ist, fügt BULK INSERT eine leere Zeichenfolge ein, wenn die Textdatei keinen Wert enthält.“
Im folgenden T-SQL-Skript fügen wir der OrderDate-Spalte eine Check-Einschränkung hinzu, die das Bestelldatum nach dem 01.01.2016 steuert.
DROP TABLE IF EXISTS Sales
CREATE TABLE [dbo].[Sales](
[Region] [varchar](50) ,
[Country] [varchar](50) ,
[ItemType] [varchar](50) NULL,
[SalesChannel] [varchar](50) NULL,
[OrderPriority] [varchar](50) NULL,
[OrderDate] datetime,
[OrderID] bigint NULL,
[ShipDate] datetime,
[UnitsSold] float,
[UnitPrice] float,
[UnitCost] float,
[TotalRevenue] float,
[TotalCost] float,
[TotalProfit] float
)
ALTER TABLE [Sales] ADD CONSTRAINT OrderDate_Check
CHECK(OrderDate >'20160101')
BULK INSERT Sales
FROM 'C:\1500000 Sales Records.csv'
WITH (FIRSTROW = 2,
FIELDTERMINATOR = ',',
ROWTERMINATOR='\n'
);
GO
SELECT COUNT(*) AS [UnChekedData] FROM
Sales WHERE OrderDate <'20160101'
Infolgedessen überspringt der Masseneinfügeprozess die Check-Constraint-Steuerung. SQL Server zeigt jedoch die Prüfeinschränkung als nicht vertrauenswürdig an:
SELECT is_not_trusted ,* FROM sys.check_constraints where name='OrderDate_Check'
Dieser Wert gibt an, dass jemand einige Daten in diese Spalte eingefügt oder aktualisiert hat, indem er die Check-Einschränkung übersprungen hat. Gleichzeitig kann diese Spalte inkonsistente Daten bezüglich dieser Einschränkung enthalten.
Versuchen Sie, die Bulk-Insert-Anweisung mit der Option CHECK_CONSTRAINTS auszuführen. Das Ergebnis ist einfach:Check Constraint gibt einen Fehler wegen falscher Daten zurück.
TABLOCK – Steigerung der Leistung bei mehreren Masseneinfügungen in eine Zieltabelle
Der Hauptzweck des Sperrmechanismus in SQL Server besteht darin, die Datenintegrität zu schützen und sicherzustellen. Im Hauptkonzept des Artikels zum Sperren von SQL Server finden Sie Details zum Sperrmechanismus.
Wir werden uns auf Details zum Sperren von Bulk-Insert-Prozessen konzentrieren.
Wenn Sie die Masseneinfügeanweisung ohne die Option TABLELOCK ausführen, erwirbt sie die Sperre von Zeilen oder Tabellen gemäß der Sperrhierarchie. In einigen Fällen möchten wir jedoch möglicherweise mehrere Bulk-Insert-Prozesse für eine Zieltabelle ausführen und so die Vorgangszeit verkürzen.
Zuerst führen wir zwei Bulk-Insert-Anweisungen gleichzeitig aus und analysieren das Verhalten des Sperrmechanismus. Öffnen Sie zwei Abfragefenster in SQL Server Management Studio und führen Sie die folgenden Bulk-Insert-Anweisungen gleichzeitig aus.
BULK INSERT Sales
FROM 'C:\1500000 Sales Records.csv'
WITH (FIRSTROW = 2,
FIELDTERMINATOR = ',',
ROWTERMINATOR='\n'
);
Führen Sie die folgende DMV-Abfrage (Dynamic Management View) aus – sie hilft, den Status des Masseneinfügungsprozesses zu überwachen:
SELECT session_id,command ,status,last_wait_type,text FROM sys.dm_exec_requests cross apply
sys.dm_exec_sql_text(sys.dm_exec_requests.sql_handle)
where text like '%BULK INSERT Sales%' and session_id <>@@SPID
Wie Sie im obigen Bild, Sitzung 61, sehen können, wird der Status des Masseneinfügungsprozesses aufgrund einer Sperrung ausgesetzt. Wenn wir das Problem überprüfen, sperrt Sitzung 59 die Zieltabelle für Masseneinfügungen. Dann wartet Sitzung 61 auf die Freigabe dieser Sperre, um den Masseneinfügungsprozess fortzusetzen.
Jetzt fügen wir die TABLOCK-Option zu den Bulk-Insert-Anweisungen hinzu und führen die Abfragen aus.
Wenn wir die DMV-Überwachungsabfrage erneut ausführen, sehen wir keinen angehaltenen Masseneinfügungsprozess, da SQL Server einen bestimmten Sperrtyp namens Bulk Update Lock (BU) verwendet. Dieser Sperrtyp ermöglicht die gleichzeitige Verarbeitung mehrerer Masseneinfügungsvorgänge für dieselbe Tabelle. Diese Option verringert auch die Gesamtzeit des Masseneinfügevorgangs.
Wenn wir während des Bulk-Insert-Prozesses die folgende Abfrage ausführen, können wir die Sperrdetails und Sperrtypen überwachen:
SELECT dm_tran_locks.request_session_id,
dm_tran_locks.resource_database_id,
DB_NAME(dm_tran_locks.resource_database_id) AS dbname,
CASE
WHEN resource_type = 'OBJECT'
THEN OBJECT_NAME(dm_tran_locks.resource_associated_entity_id)
ELSE OBJECT_NAME(partitions.OBJECT_ID)
END AS ObjectName,
partitions.index_id,
indexes.name AS index_name,
dm_tran_locks.resource_type,
dm_tran_locks.resource_description,
dm_tran_locks.resource_associated_entity_id,
dm_tran_locks.request_mode,
dm_tran_locks.request_status
FROM sys.dm_tran_locks
LEFT JOIN sys.partitions ON partitions.hobt_id = dm_tran_locks.resource_associated_entity_id
LEFT JOIN sys.indexes ON indexes.OBJECT_ID = partitions.OBJECT_ID AND indexes.index_id = partitions.index_id
WHERE resource_associated_entity_id > 0
AND resource_database_id = DB_ID()
Schlussfolgerung
Der aktuelle Artikel untersuchte alle Details des Masseneinfügungsvorgangs in SQL Server. Insbesondere haben wir den Befehl BULK INSERT und seine Einstellungen und Optionen erwähnt. Außerdem haben wir verschiedene Szenarien analysiert, die realen Problemen nahe kommen.
Nützliches Tool:
dbForge Data Pump – ein SSMS-Add-In zum Füllen von SQL-Datenbanken mit externen Quelldaten und zum Migrieren von Daten zwischen Systemen.