Résoudre les problèmes liés à mssql-python

Diagnostiquez et résolvez les problèmes courants lors de l’utilisation du pilote mssql-python pour vous connecter à SQL Server, Azure SQL Database, Azure SQL Managed Instance et SQL Database dans Microsoft Fabric.

Problèmes d’installation

pip échoue à l’installation ou compile à partir du code source

Symptômes :

error: Microsoft Visual C++ 14.0 or greater is required
ERROR: Failed building wheel for mssql-python

Causes et solutions possibles :

  • Pas de volant préassemblé pour votre plateforme

    • Vérifiez que vous utilisez une version Python prise en charge (versions 3.10 et ultérieures) et une plateforme. Voir cycle de vie du support pour la matrice de compatibilité. Mettez à jour le pip avant d’installer avec pip install --upgrade pip. Pour les environnements d’équipe reproductibles, utilisez le flux de travail verrouillé dans les déploiements répétables ou les patrons de conteneurs dans le contenu et le développement local afin de réduire la dérive locale des machines.
  • Environnement virtuel non activé

    • Activez d’abord votre environnement virtuel. Installer Python dans le système peut entraîner des erreurs d’autorisation ou des conflits.
    python -m venv .venv
    .venv\Scripts\activate
    pip install mssql-python
    

Installations de pilotes en conflit

Symptômes :

Erreurs d’importation ou comportements inattendus après une installation mssql-python parallèle pyodbc dans le même environnement.

Correctif :

mssql-python et pyodbc peuvent coexister. Si vous constatez des conflits, créez un environnement virtuel propre :

python -m venv .venv --clear
.venv\Scripts\activate
pip install mssql-python

Problèmes de connexion

Impossible de se connecter au serveur

Symptômes :

OperationalError: [08001] (0) Client unable to establish connection

Causes et solutions possibles :

  • Serveur injoignable

    • Vérifiez que le nom du serveur et le port sont corrects.
    • Vérifiez la connectivité réseau : ping servername ou telnet servername 1433.
    • Assurez-vous que le pare-feu autorise les connexions sortantes sur le port 1433.
  • SQL Server ne fonctionne pas

    • Vérifiez que le service SQL Server a été lancé.
    • Pour les instances nommées, vérifiez que le service SQL Server Browser fonctionne.
  • Règles de pare-feu Azure SQL

    • Ajoutez l’IP de votre client aux règles du pare-feu Azure SQL dans le portail Azure.
    • Pour Azure SQL Managed Instance, assurez-vous de vous connecter depuis un réseau autorisé.
# Test basic connectivity
import socket
try:
    sock = socket.create_connection(("<server>.database.windows.net", 1433), timeout=5)
    print("TCP connection successful")
    sock.close()
except Exception as e:
    print(f"Cannot reach server: {e}")

Échec de la connexion

Symptômes :

OperationalError: [28000] (18456) Login failed for user 'username'.

Causes et solutions possibles :

  • Non-correspondance du mode d’authentification

    • Pour Azure SQL Database, Azure SQL Managed Instance et SQL Database dans Fabric, il faut privilégier un mode Microsoft Entra tel que Authentication=ActiveDirectoryDefault.
    • Si vous utilisez intentionnellement l’authentification SQL, vérifiez que le serveur le permet et que vous utilisez le bon format de connexion pour ce point de terminaison.
  • Identifiants d’authentification SQL incorrects

    • Vérifiez le nom d’utilisateur et le mot de passe.
    • Pour Azure SQL, incluez le nom d’utilisateur complet : username@servername.
  • L’utilisateur n’existe pas dans la base de données

    • Vérifiez que l’utilisateur a accès à la base de données spécifiée.
    • Vérifiez si la connexion est associée à un utilisateur de base de données.
  • Authentification non configurée

    • Utilisez l’authentification Microsoft Entra (recommandée) : Authentication=ActiveDirectoryDefault.
    • Si vous dépannez un SQL Server local qui devrait accepter l'authentification SQL, vérifiez que SQL Server utilise une authentification en mode mixte.

Délai d’expiration de la connexion

Symptômes :

OperationalError: [HYT00] (0) Timeout expired
OperationalError: [HYT01] (0) Connection timeout expired

Causes et solutions possibles :

  • Le serveur répond lentement

    • Augmenter le délai de connexion :
    conn = mssql_python.connect(connection_string, timeout=60)
    
  • Latence du réseau

    • Vérifie le chemin réseau vers le serveur.
    • Envisagez d’utiliser un chemin réseau plus court ou un VPN.
  • Serveur sous forte charge

    • Essayez de vous connecter pendant les heures creuses.
    • Contactez votre administrateur de base de données.

Erreurs de certificat SSL

Symptômes :

OperationalError: [08001] SSL Provider: The certificate chain was issued by an authority that is not trusted

Solutions :

Premièrement, privilégiez un certificat de confiance ou les schémas de développement local dans Conteneur et le développement local. Utilisez-les TrustServerCertificate=yes uniquement pour le développement local sur un serveur que vous contrôlez.

Pour le développement et les tests avec un certificat auto-signé :

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
    "TrustServerCertificate=yes;"  # Don't use in production
)

Caution

TrustServerCertificate=yes est une solution de secours uniquement locale. Ne l’emportez pas dans des devcontainers partagés, des pipelines CI ou des déploiements en production. Pour des conseils plus larges, voir Chiffrement et certificats.

Pour la production, assurez-vous que les certificats appropriés sont installés et utilisez :

conn = mssql_python.connect(
    "Server=<server>.database.windows.net;"
    "Database=<database>;"
    "Authentication=ActiveDirectoryDefault;"
    "Encrypt=yes;"
    "HostnameInCertificate=<server>.domain.com;"
)

Problèmes d’exécution des requêtes

Table ou objet non trouvé

Symptômes :

ProgrammingError: [42S02] (208) Invalid object name 'TableName'.

Causes et solutions possibles :

  • Mauvais contexte de base de données

    # Ensure you're connected to the correct database
    cursor.execute("SELECT DB_NAME()")
    print(cursor.fetchone()[0])
    
  • Schéma non spécifié

    # Use fully qualified name
    cursor.execute("SELECT * FROM dbo.TableName")
    
  • La table n’existe pas

    # Check if table exists
    cursor.execute("""
         SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES 
         WHERE TABLE_NAME = 'TableName'
    """)
    

Erreur de syntaxe

Symptômes :

ProgrammingError: [42000] (102) Incorrect syntax near '...'.

Solutions :

  1. Testez d’abord SQL dans SSMS pour vérifier la syntaxe

  2. Vérifiez l’échappement des chaînes de caractères - utilisez des requêtes paramétrées :

    # Wrong - vulnerable to syntax issues and SQL injection
    cursor.execute(f"SELECT * FROM Production.Product WHERE Name = '{name}'")
    
    # Correct - use parameters
    cursor.execute("SELECT * FROM Production.Product WHERE Name = %(name)s", {"name": name})
    

Erreurs de paramètres

Symptômes :

ProgrammingError: [07001] Wrong number of parameters

Solutions :

  1. Comptez les points de placement et les paramètres – ils doivent correspondre

  2. Choisissez le bon style de paramètres :

    # Qmark style - positional
    cursor.execute("SELECT * FROM Production.Product WHERE ProductID = ? AND Name LIKE ?", (1, "Adjustable%"))
    print(cursor.fetchone())
    
    # Pyformat style - named
    cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(id)s AND Name LIKE %(name)s", {"id": 1, "name": "Adjustable%"})
    print(cursor.fetchone())
    

Problèmes de type de données

Erreurs de conversion de date et d’heure

Symptômes :

DataError: [22007] Invalid datetime format

Solutions :

Utilisez des objets datatime Python au lieu de chaînes de caractères :

from datetime import datetime

cursor.execute("CREATE TABLE #Events (EventDate DATETIME)")

# Wrong - this raises an error for invalid dates
try:
    cursor.execute("INSERT INTO #Events (EventDate) VALUES (%(event_date)s)", {"event_date": "2024-13-45"})
except Exception as e:
    print(f"Expected error: {e}")

# Correct - use Python datetime objects
cursor.execute("INSERT INTO #Events (EventDate) VALUES (%(event_date)s)", {"event_date": datetime(2024, 3, 15)})
cursor.execute("SELECT EventDate FROM #Events")
print(cursor.fetchone())

Problèmes de précision décimale

Symptômes :

Les chiffres apparaissent tronqués ou arrondis incorrectement.

Solutions :

Utilisation decimal.Decimal pour des valeurs numériques précises :

from decimal import Decimal

cursor.execute("CREATE TABLE #PriceDemo (ListPrice DECIMAL(10,2))")
# Preserve full precision
cursor.execute(
    "INSERT INTO #PriceDemo (ListPrice) VALUES (%(list_price)s)",
    {"list_price": Decimal("19.99")}
)

Problèmes d’encodage Unicode

Symptômes :

Les caractères spéciaux apparaissent brouillés ou causent des erreurs.

Solutions :

  1. Utilisez les colonnes NVARCHAR pour les données Unicode dans votre base de données

  2. Passez directement les chaînes - le driver gère l’encodage :

    cursor.execute("CREATE TABLE #UnicodeDemo (Name NVARCHAR(50))")
    cursor.execute("INSERT INTO #UnicodeDemo (Name) VALUES (%(name)s)", {"name": "日本語"})
    cursor.execute("SELECT Name FROM #UnicodeDemo")
    print(cursor.fetchone())
    

Problèmes de performance

Exécution lente des requêtes

Causes et solutions possibles :

  • Index manquants : Vérifiez le plan d’exécution des requêtes dans SSMS.

  • Grands ensembles de résultats : Utiliser fetchmany() au lieu de fetchall():

    cursor.arraysize = 1000
    while True:
         rows = cursor.fetchmany()
         if not rows:
             break
         process_rows(rows)
    
  • Pool de connexions désactivé : Activer le pool de connexions :

    import mssql_python
    mssql_python.pooling(max_size=20, idle_timeout=300)
    

Problèmes de mémoire avec des résultats volumineux

Symptômes :

Le processus Python manque de mémoire.

Solutions :

  1. Résultats du flux au lieu de tout charger en mémoire :

    cursor.execute("SELECT * FROM LargeTable")
    for row in cursor:  # Iterates one row at a time
        process_row(row)
    
  2. Utilisez la pagination côté serveur :

    page_size = 1000
    offset = 0
    while True:
        cursor.execute(
            "SELECT * FROM LargeTable ORDER BY ID "
            "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY",
            (offset, page_size)
        )
        rows = cursor.fetchall()
        if not rows:
            break
        process_rows(rows)
        offset += page_size
    

Problèmes de transaction

Portée de table temporaire avec autocommit

Les tables temporaires (#tablename) créées à l’intérieur d’une transaction disparaissent lorsque celle-ci est annulée. C’est une source fréquente de confusion lorsque l’autocommit est désactivé (par défaut) :

conn = mssql_python.connect(connection_string)  # autocommit=False by default
cursor = conn.cursor()

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
cursor.execute("INSERT INTO #TempData VALUES (1, 'test')")

# If the connection rolls back (explicit or on error), #TempData disappears
conn.rollback()

# This fails: Invalid object name '#TempData'
cursor.execute("SELECT * FROM #TempData")

Correction : Commet immédiatement après avoir créé une table temporaire, ou utilise le mode autocommit :

cursor.execute("CREATE TABLE #TempData (ID INT, Name NVARCHAR(50))")
conn.commit()  # Lock in the table definition

cursor.execute("INSERT INTO #TempData VALUES (1, 'test')")
conn.commit()

Les instructions DDL qui nécessitent le mode de validation automatique, telles que CREATE DATABASE, échouent dans une transaction ouverte. Définissez l’autocommit avant de les exécuter :

conn.autocommit = True
cursor.execute("CREATE DATABASE TestDB")
conn.autocommit = False

Transaction non engagée

Symptômes :

Les changements de données ne persistent pas après la fermeture de la connexion.

Solution:

Avec autocommit=False (par défaut), vous devez appeler commit():

cursor.execute("CREATE TABLE #Products (Name NVARCHAR(100))")
cursor.execute("INSERT INTO #Products (Name) VALUES (%(name)s)", {"name": "Widget"})
conn.commit()  # Don't forget this!

Ou utilisez le mode d’engagement automatique :

conn = mssql_python.connect(connection_string, autocommit=True)

Erreurs d’interblocage

Symptômes :

OperationalError: [40001] (1205) Transaction ... was deadlocked on lock resources with another process

Solution:

La logique de réessai (voir logique de réessayage) gère l’échec immédiat, mais des blocages récurrents indiquent un problème de conception. Pour résoudre la cause profonde, capturez le graphique de blocage et analysez quelles instructions et types de verrous sont impliqués. Les solutions courantes incluent la réorganisation des opérations afin que les transactions concurrentes acquièrent des verrous dans la même séquence, la réduction de l’étendue des transactions et l’ajout d’indices appropriés pour diminuer la durée de verrouillage.

Pour un guide complet de l’analyse des blocages, consultez le guide des blocages. Si vous utilisez Azure SQL Database, consultez Analyser et prévenir les blocages.

Problèmes de chargement en masse

Violations de contraintes lors d’une copie en bloc

Symptômes :

RuntimeError: CHECK constraint ... Conflict occurred in database ...
RuntimeError: Cannot insert duplicate key ... violation of PRIMARY KEY constraint

Cause :

Les données de votre lot violent les contraintes de la table (clé primaire, unique, CHECK ou clé étrangère).

Correctif :

Validez les données avant de charger. Pour les grands ensembles de données, chargez-les d’abord dans une table intermédiaire, puis fusionnez-les dans la table cible :

# Load into staging, then validate
cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(100))")
cursor.bulkcopy("##Staging", rows)

# Check for duplicates before merging
cursor.execute("""
    SELECT s.ID FROM ##Staging s
    INNER JOIN dbo.Target t ON s.ID = t.ID
""")
dupes = cursor.fetchall()
if dupes:
    print(f"Skipping {len(dupes)} duplicate rows")

# Insert only non-duplicate rows
cursor.execute("""
    INSERT INTO dbo.Target (ID, Name)
    SELECT s.ID, s.Name FROM ##Staging s
    WHERE NOT EXISTS (SELECT 1 FROM dbo.Target t WHERE t.ID = s.ID)
""")
conn.commit()

Pour les modèles d’upsert avec des tables intermédiaires, voir Modèles de chargement et de déplacement des données.

Erreurs de correspondance de colonnes

Symptômes :

RuntimeError: Bulk copy failure - column count mismatch

Cause :

Le nombre de colonnes dans vos données ne correspond pas au nombre de colonnes de la table cible, ou les colonnes sont dans le mauvais ordre.

Correctif :

Assurez-vous que vos données correspondent exactement au schéma de la table dans l’ordre et le nombre :

# Check the target table schema
cursor.execute("""
    SELECT COLUMN_NAME, DATA_TYPE
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_NAME = 'MyTable'
    ORDER BY ORDINAL_POSITION
""")
for col in cursor.fetchall():
    print(col)

# Match your data to the column order
rows = [
    (1, "Widget", Decimal("19.99")),  # Must match table column order
    (2, "Gadget", Decimal("29.99")),
]
cursor.bulkcopy("dbo.MyTable", rows)

Désaccords de type lors de la copie en vrac

Symptômes :

Les données se chargent mais les valeurs sont tronquées, arrondies ou incorrectes.

Cause :

Les valeurs de Python ne correspondent pas clairement aux types de colonnes cibles. Cas courants : float valeurs chargées dans decimal des colonnes (perte de précision), ou chaînes surdimensionnées chargées dans des colonnes de longueur fixe.

Correctif :

Utilisez les bons types de Python qui correspondent à votre schéma :

from decimal import Decimal

# Use Decimal for decimal/numeric columns, not float
rows = [
    (1, "Widget", Decimal("19.99")),  # Correct
    # (1, "Widget", 19.99),           # Avoid: float loses precision
]
cursor.bulkcopy("dbo.Products", rows)

Échecs de liaison de type NumPy

Symptômes :

Les paramètres échouent silencieusement ou suscitent des erreurs de type de données lorsqu’on utilise des types numpy integer ou float.

Cause :

Les types NumPy comme numpy.int64 et numpy.int32 ne satisfont pas à isinstance(x, int) dans NumPy 2.x. L’inférence de type du conducteur ne les reconnaît pas, ce qui provoque un comportement inattendu.

Correctif :

Convertir les valeurs numpy en types natifs Python avant de lier :

import numpy as np

# Convert individual values
cursor.execute("SELECT * FROM Production.Product WHERE ProductID = %(product_id)s", {"product_id": int(np.int64(42))})

# Convert DataFrame values
for _, row in df.iterrows():
    cursor.execute(
        "INSERT INTO #Orders (ProductID, Qty) VALUES (%(product_id)s, %(qty)s)",
        {"product_id": int(row["ProductID"]), "qty": int(row["Qty"])}
    )

Pour les ensembles de données plus volumineux, utilisez plutôt les chemins d’intégration Arrow ou pandas , qui gèrent la conversion de type en interne.

Bulkcopy avec tables temporaires

Symptômes :

cursor.bulkcopy("#TempTable", data) déclenche RuntimeError: Invalid object name '#TempTable'.

Cause :

bulkcopy() Je ne peux pas résoudre les tables temporaires de session (#tablename) à cause des limitations de recherche de métadonnées. Les tables temporaires globales (##tablename) et les tables permanentes fonctionnent.

Correctif :

Utilisez une table temporaire globale ou une table de mise en scène classique :

# Global temp table (visible to all sessions, dropped when last session disconnects)
cursor.execute("CREATE TABLE ##Staging (ID INT, Name NVARCHAR(50))")
cursor.bulkcopy("##Staging", rows)

# Or use a permanent staging table
cursor.execute("CREATE TABLE dbo.Staging (ID INT, Name NVARCHAR(50))")
cursor.bulkcopy("dbo.Staging", rows)

Pour de petits ensembles de données où une table temporaire de session est préférée, utilisez executemany() plutôt :

cursor.execute("CREATE TABLE #Staging (ID INT, Name NVARCHAR(50))")
cursor.executemany("INSERT INTO #Staging (ID, Name) VALUES (?, ?)", rows)

Problèmes liés aux conteneurs et à la CI

Bibliothèques système manquantes sur Linux

Symptômes :

ImportError: libltdl.so.7: cannot open shared object file: No such file or directory
ImportError: libkrb5.so.3: cannot open shared object file

Correctif :

Installez les packs système nécessaires. Les paquets diffèrent selon leur distribution :

Distribution Commande d'installation
Ubuntu/Debian sudo apt-get install libltdl7 libkrb5-3 libgssapi-krb5-2
Red Hat / Fedora sudo dnf install libtool-ltdl krb5-libs
Alpine apk add libltdl krb5-libs

Pour des exemples de Dockerfile, voir Conteneur et développement local.

Erreurs SSL de macOS après installation

Symptômes :

Des erreurs liées à SSL lors de la connexion depuis macOS, en particulier sur Apple Silicon.

Correctif :

Installez OpenSSL via Homebrew et définissez les drapeaux de liaison :

brew install openssl
export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
export CPPFLAGS="-I/opt/homebrew/opt/openssl/include"

Outils de diagnostic

Activer la journalisation des pilotes

Utilisez mssql_python.setup_logging() pour activer la journalisation DEBUG complète à des fins de dépannage. Toutes les opérations de pilote sont enregistrées, y compris les instructions SQL, les paramètres, les opérations ODBC internes et les changements d’état de connexion.

import mssql_python

# Enable logging to file (default)
mssql_python.setup_logging()

# Output to stdout (useful for CI/CD and containers)
mssql_python.setup_logging(output='stdout')

# Output to both file and stdout
mssql_python.setup_logging(output='both')

# Custom log file path (must use .txt, .log, or .csv extension)
mssql_python.setup_logging(log_file_path="/var/log/myapp/mssql.log")

Les fichiers journaux sont enregistrés au format CSV et font l’objet d’une rotation automatique lorsqu’ils atteignent 512 Mo, avec conservation de cinq fichiers de sauvegarde. Les données sensibles, comme les mots de passe et les jetons d’accès, sont automatiquement masquées dans la sortie des journaux.

Pour ajouter vos propres entrées de journal aux côtés des journaux des pilotes, utilisez driver_logger:

from mssql_python.logging import driver_logger

mssql_python.setup_logging()

driver_logger.debug("[App] Starting data processing")
driver_logger.error("[App] Failed to process record")
# Your entries appear in the same file with the same format

Caution

La journalisation comporte une surcharge de performance. Activez-le uniquement lors du dépannage, pas en production par défaut.

Obtenez les informations du conducteur

Récupérez la version du pilote et les détails du serveur depuis une connexion active :

import mssql_python

conn = mssql_python.connect(connection_string)

# Driver version
print(f"Version: {mssql_python.__version__}")

# Server information
print(f"Server name: {conn.getinfo(mssql_python.SQL_SERVER_NAME)}")
print(f"Database name: {conn.getinfo(mssql_python.SQL_DATABASE_NAME)}")

Vérifier l’état de la connexion

Testez si une connexion est toujours ouverte avant de tenter les opérations :

try:
    cursor = conn.cursor()
    cursor.execute("SELECT 1")
    print("Connection is open")
except mssql_python.Error:
    print("Connection is closed or broken")

Référence rapide : Erreurs courantes

Error SQLSTATE Cause courante Correctif rapide
Le client ne peut pas établir de connexion 08001 Serveur inaccessible Vérifier le nom du serveur/port
Échec de la connexion 28000 Mauvais titres d’identité Vérifier le nom d’utilisateur/mot de passe
Délai expiré HYT00/HYT01 Réseau lent Augmenter le délai d’attente
Nom d’objet non valide 42S02 Mauvaise table/schéma Utilisez des noms pleinement qualifiés
Erreur de syntaxe 42000 Erreur SQL Utiliser des requêtes paramétrables
Violation de contrainte 23000 Violation FK/PK Vérifier l’intégrité des données
Deadlock 40001 Contention de verrouillage Réessayez, puis analysez le graphique de blocage