Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Il driver mssql-python include una funzione di copia in massa che inserisce in modo efficiente grandi quantità di dati in SQL Server, database SQL di Azure, Istanza gestita di SQL di Azure e database SQL in Microsoft Fabric.
Il cursor.bulkcopy() metodo fornisce un percorso ad alte prestazioni per il caricamento di grandi dataset:
- Minimizza i viaggi di andata e ritorno in rete.
- Opzionalmente bypassa il controllo dei vincoli durante il caricamento.
- Utilizza il protocollo ottimizzato TDS bulk insert.
- Raggiunge una portata paragonabile a
bcp.exeeSqlBulkCopy.
L'estensione nativa basata mssql_py_core su Rust alimenta la funzione di copia in massa. Viene eseguito al di fuori della normale pipeline del cursore execute().
Utilizzo di base
Chiama bulkcopy() su un cursore, passando il nome della tabella di destinazione e un iterabile di tuple di righe o oggetti Row:
Importante
Se crei o modifichi la tabella di destinazione nella stessa sessione, chiama conn.commit() prima di bulkcopy(). Il protocollo bulk copy utilizza un canale interno separato per leggere i metadati della tabella, quindi una modifica DDL non commessa può causare un deadlock o un timeout.
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
# Create a temp table for the demo
cursor.execute("""
CREATE TABLE ##BulkDemo (
ID INT,
Name NVARCHAR(50),
Amount MONEY
)
""")
conn.commit()
data = [
(1, "Alice", 50000.00),
(2, "Bob", 60000.00),
(3, "Carol", 55000.00),
]
result = cursor.bulkcopy("##BulkDemo", data)
print(f"Copied {result['rows_copied']} rows in {result['batch_count']} batch(es)")
print(f"Elapsed: {result['elapsed_time']}")
Valore restituito
bulkcopy() restituisce un dizionario:
| Key | TIPO | Descrizione |
|---|---|---|
rows_copied |
int | Numero di righe copiate con successo. |
batch_count |
int | Numero di lotti processati. |
elapsed_time |
float | Tempo impiegato per l'operazione in secondi. |
Firma del metodo
cursor.bulkcopy(
table_name, # str – target table (can include schema, e.g. "dbo.MyTable")
data, # Iterable[Tuple | Row] – rows to insert
batch_size=0, # int – rows per batch; 0 = server optimal
timeout=30, # int – operation timeout in seconds
column_mappings=None, # List[str] | List[Tuple[int,str]] | None
keep_identity=False, # bool – preserve identity values from source
check_constraints=False, # bool – check constraints during load
table_lock=False, # bool – use table-level lock
keep_nulls=False, # bool – preserve NULLs instead of defaults
fire_triggers=False, # bool – fire INSERT triggers on target
use_internal_transaction=False, # bool – use internal transaction per batch
)
Mappatura delle colonne
Per impostazione predefinita, bulkcopy() mappa le colonne in base alla posizione ordinale. Ogni colonna di dati corrisponde alla colonna della tabella con lo stesso indice. Usa il column_mappings parametro per sovrascrivere questo comportamento.
Elenco dei nomi delle colonne
Ogni posizione nell'elenco corrisponde all'indice dei dati di origine:
result = cursor.bulkcopy(
"##BulkDemo",
data,
column_mappings=["ID", "Name", "Amount"],
)
Formato avanzato: mappatura esplicita degli indici
Ogni tupla assume la forma (source_index, target_column_name). Usa questo formato per saltare o riordinare le colonne:
result = cursor.bulkcopy(
"##BulkDemo",
data,
column_mappings=[(0, "ID"), (1, "Name"), (2, "Amount")],
)
Caricamento dai file
Puoi caricare dati da file CSV e altri formati passando un generatore a bulkcopy().
File CSV
import csv
import io
import mssql_python
# In production, replace io.StringIO with open("data.csv", "r", ...)
csv_data = """ID,Name,Value
1,Widget,9.99
2,Gadget,24.50
3,Gizmo,4.75
"""
def csv_row_generator(file_obj):
"""Generator that yields tuples from a CSV file object."""
reader = csv.reader(file_obj)
next(reader) # Skip header
for row in reader:
if row: # skip blank lines
yield (
int(row[0]), # ID
row[1], # Name
float(row[2]), # Value
)
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##CSVImport (ID INT, Name NVARCHAR(100), Value FLOAT)
""")
conn.commit()
result = cursor.bulkcopy("##CSVImport", csv_row_generator(io.StringIO(csv_data)))
print(f"Imported {result['rows_copied']} rows from CSV")
File di grandi dimensioni con elaborazione in batch
Imposta il parametro batch_size per controllare quante righe il driver invia in ogni batch. Questo approccio funziona bene per file di grandi dimensioni:
import csv
import io
import mssql_python
# In production, replace io.StringIO with open("large_file.csv", "r", ...)
csv_data = "\n".join(
["ID,Name,Value"] + [f"{i},Item {i},{i * 1.5}" for i in range(1, 201)]
)
def csv_rows(file_obj):
reader = csv.reader(file_obj)
next(reader) # Skip header
for row in reader:
if row:
yield (int(row[0]), row[1], float(row[2]))
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##LargeCSV (ID INT, Name NVARCHAR(100), Value FLOAT)
""")
conn.commit()
result = cursor.bulkcopy(
"##LargeCSV",
csv_rows(io.StringIO(csv_data)),
batch_size=50,
)
print(f"Imported {result['rows_copied']} rows in {result['batch_count']} batches")
Carica i DataFrame di pandas
Converti un DataFrame pandas in una lista di tuple prima di passarlo a bulkcopy():
import pandas as pd
import mssql_python
df = pd.DataFrame({
'ID': [1, 2, 3],
'Name': ['Alice', 'Bob', 'Carol'],
'Amount': [50000.0, 60000.0, 55000.0],
})
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##PandasDemo (ID INT, Name NVARCHAR(50), Amount MONEY)
""")
conn.commit()
data = [tuple(row) for row in df.itertuples(index=False, name=None)]
result = cursor.bulkcopy("##PandasDemo", data)
Gestire i valori NULL
Passa None in qualsiasi posizione di colonna per inserire un valore SQL NULL :
cursor.execute("""
CREATE TABLE ##NullDemo (ID INT, Name NVARCHAR(50), Amount MONEY)
""")
conn.commit()
data = [
(1, "Alice", 50000.00),
(2, "Bob", None), # NULL Amount
(3, None, 55000.00), # NULL Name
]
cursor.bulkcopy("##NullDemo", data)
Colonne di identità
Per inserire valori identità espliciti, impostare keep_identity=True:
cursor.execute("""
CREATE TABLE ##IdentDemo (ID INT, Name NVARCHAR(50), Amount MONEY)
""")
conn.commit()
data = [
(100, "Alice", 50000.00),
(200, "Bob", 60000.00),
]
cursor.bulkcopy("##IdentDemo", data, keep_identity=True)
Quando keep_identity=False (il valore predefinito), ometti la colonna identità dai tuoi dati e usala column_mappings per indirizzare le colonne non identità.
Opzioni di copia in blocco
| Parametro | Impostazione predefinita | Descrizione |
|---|---|---|
batch_size |
0 |
Righe per blocco.
0 Permette al server di scegliere la dimensione ottimale. |
timeout |
30 |
Timeout dell'operazione in secondi. |
keep_identity |
False |
Preservare i valori ID dei dati di origine. |
check_constraints |
False |
Controllare i vincoli della tabella durante il caricamento. |
table_lock |
False |
Acquisire un blocco a livello di tabella invece di blocchi a livello di riga. |
keep_nulls |
False |
Preservare i valori NULL invece di inserire valori predefiniti delle colonne. |
fire_triggers |
False |
Attiva INSERT trigger sulla tabella di destinazione. |
use_internal_transaction |
False |
Racchiudi ogni batch in una transazione interna. |
Gestire gli errori
bulkcopy() solleva un'eccezione se il carico fallisce, quindi avvolgi la chiamata in un try/except blocco per rilevare gli errori. Tieni presente che bulkcopy() viene eseguito su una propria connessione interna e conferma le righe copiate in modo indipendente, quindi un conn.rollback() sulla connessione principale non può annullarle. Per rendere atomico un batch, imposta use_internal_transaction=True, che racchiude ogni batch nella propria transazione, che viene automaticamente annullata se il batch non riesce:
import mssql_python
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##ImportDemo (ID INT, Name NVARCHAR(50), Value FLOAT)
""")
conn.commit()
data = [
(1, "Alice", 50000.00),
(2, "Bob", 60000.00),
(3, "Carol", 55000.00),
]
try:
result = cursor.bulkcopy("##ImportDemo", data, use_internal_transaction=True)
print(f"Successfully copied {result['rows_copied']} rows")
except (mssql_python.DatabaseError, ValueError) as e:
# bulkcopy() commits on its own connection, so there's nothing to roll back
# here. With use_internal_transaction=True, a failed batch is already rolled
# back on the bulk copy connection.
print(f"Bulk copy failed: {e}")
Per subordinare un caricamento alla tua logica di convalida, esegui una copia in blocco in una tabella di staging, quindi sposta le righe nella tabella di destinazione con un'INSERT ... SELECT all'interno di una transazione nella connessione principale. Questo INSERT funziona sulla tua connessione, quindi conn.rollback() annulla se la validazione fallisce.
Authentication
La copia di massa utilizza un canale interno separato che richiede un proprio token. Il driver gestisce automaticamente l'acquisizione dei token per i metodi di autenticazione supportati.
Identità gestita (ActiveDirectoryMSI)
Utilizzare Authentication=ActiveDirectoryMSI per identità gestita assegnata dal sistema o dall'utente. Questo metodo di autenticazione è consigliato per servizi ospitati su Azure come Azure VM, App Service, Functions e AKS.
import mssql_python
# System-assigned managed identity
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryMSI;"
"Encrypt=yes"
)
cursor = conn.cursor()
cursor.execute("CREATE TABLE ##MsiDemo (ID INT, Name NVARCHAR(50))")
conn.commit()
result = cursor.bulkcopy("##MsiDemo", [(1, "Alice"), (2, "Bob")])
print(f"Copied {result['rows_copied']} rows")
Per un'identità gestita assegnata dall'utente, passa l'ID client nella stringa di connessione:
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryMSI;"
"UID=<client-id>;"
"Encrypt=yes"
)
Principale di servizio (ActiveDirectoryServicePrincipal)
Utilizzare Authentication=ActiveDirectoryServicePrincipal per l'autenticazione del principale servizio (credenziali client).
conn = mssql_python.connect(
"Server=<server>.database.windows.net;"
"Database=<database>;"
"Authentication=ActiveDirectoryServicePrincipal;"
"UID=<application-client-id>;"
"PWD=<client-secret>;"
"Encrypt=yes"
)
cursor = conn.cursor()
cursor.execute("CREATE TABLE ##SpDemo (ID INT, Value FLOAT)")
conn.commit()
result = cursor.bulkcopy("##SpDemo", [(1, 1.5), (2, 2.5)])
print(f"Copied {result['rows_copied']} rows")
Catena di credenziali predefinita (ActiveDirectoryDefault)
ActiveDirectoryDefault Prova più fornitori di credenziali in sequenza, come variabili ambientali, identità del carico di lavoro, identità gestita e altro ancora. Funziona sia per lo sviluppo locale che per i servizi ospitati su Azure senza modifiche al codice.
Per maggiori informazioni sull'autenticazione, vedi Microsoft Entra autentication.
Suggerimenti per le prestazioni
Le seguenti tecniche ti aiutano a massimizzare la velocità di copia in massa.
Usa generatori per grandi dataset
I generatori minimizzano l'uso della memoria perché bulkcopy() accettano qualsiasi iterabile:
def data_generator(count):
"""Generate rows without loading all into memory."""
for i in range(count):
yield (i, f"Item {i}", i * 1.5)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##LargeDemo (ID INT, Name NVARCHAR(50), Value FLOAT)
""")
conn.commit()
result = cursor.bulkcopy("##LargeDemo", data_generator(1000))
Usa serrature da tavolo per carichi più veloci
Quando non hai lettori concorrenti, imposta table_lock=True per ridurre il sovraccarico di blocco durante carichi iniziali elevati.
result = cursor.bulkcopy(
"##LargeDemo",
data,
table_lock=True,
batch_size=100000,
)
Disabilita gli indici durante il caricamento
Disabilita temporaneamente gli indici non clusterizzati prima del caricamento in massa e ricostruiscili successivamente per migliorare le prestazioni:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE ##IndexDemo (ID INT, Name NVARCHAR(50), Value FLOAT)
""")
cursor.execute("CREATE NONCLUSTERED INDEX IX_Name ON ##IndexDemo(Name)")
conn.commit()
cursor.execute("ALTER INDEX IX_Name ON ##IndexDemo DISABLE")
conn.commit()
result = cursor.bulkcopy("##IndexDemo", data)
conn.commit()
cursor.execute("ALTER INDEX IX_Name ON ##IndexDemo REBUILD")
conn.commit()
Tabelle di carico in parallelo
Apri una connessione separata per ogni tabella ed esegui i carichi contemporaneamente.
import concurrent.futures
def load_table(table_name, rows):
conn = mssql_python.connect(connection_string)
cursor = conn.cursor()
cursor.execute(f"CREATE TABLE {table_name} (ID INT, Name NVARCHAR(50), Value FLOAT)")
conn.commit()
result = cursor.bulkcopy(table_name, rows)
conn.commit()
conn.close()
return result["rows_copied"]
data = [(i, f"Item {i}", i * 1.5) for i in range(100)]
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
futures = [
executor.submit(load_table, "##Load1", data),
executor.submit(load_table, "##Load2", data),
executor.submit(load_table, "##Load3", data),
]
for future in concurrent.futures.as_completed(futures):
print(f"Loaded {future.result()} rows")
Confronto con le alternative
La tabella seguente confronta il copio di massa con altri metodi di inserimento dati.
| metodo | Caso di utilizzo | Prestazioni |
|---|---|---|
cursor.bulkcopy() |
Grandi dataset (più di 1.000 righe). | Il più veloce |
cursor.executemany() |
Dataset medi con parametri. | Moderate |
cursor.execute() in un ciclo |
Piccoli dataset con una logica semplice. | Più lento |