Ошибка MySQL.Insert - MySqlClient.MySqlException (0x80004005)

Периодически раз в 2-5 дней у приложения возникает ошибка:

MySql.Data.MySqlClient.MySqlException (0x80004005): Fatal error encountered during command execution. ---> System.IO.IOException: Не удается записать данные в транспортное соединение: Программа на вашем хост-компьютере разорвала установленное подключение. ---> System.Net.Sockets.SocketException: Программа на вашем хост-компьютере разорвала установленное подключение
   в System.Net.Sockets.Socket.Send(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags)
   в System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   --- Конец трассировки внутреннего стека исключений ---
   в System.Net.Sockets.NetworkStream.Write(Byte[] buffer, Int32 offset, Int32 size)
   в MySql.Data.MySqlClient.TimedStream.<WriteAsync>d__36.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.TimedStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   в MySql.Data.MySqlClient.MySqlStream.<SendPacketAsync>d__33.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.NativeDriver.<ExecutePacketAsync>d__53.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.NativeDriver.<SendQueryAsync>d__42.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.Driver.<SendQueryAsync>d__83.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.Statement.<ExecuteNextAsync>d__26.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.PreparableStatement.<ExecuteNextAsync>d__19.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.Statement.<ExecuteAsync>d__25.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.PreparableStatement.<ExecuteAsync>d__18.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)
   в MySql.Data.MySqlClient.MySqlCommand.<ExecuteReaderAsync>d__111.MoveNext()
   в MySql.Data.MySqlClient.MySqlCommand.<ExecuteReaderAsync>d__111.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   в MySql.Data.MySqlClient.MySqlCommand.<ExecuteReaderAsync>d__111.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.MySqlCommand.<ExecuteNonQueryAsync>d__100.MoveNext()
--- Конец трассировка стека из предыдущего расположения, где возникло исключение ---
   в System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   в System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   в MySql.Data.MySqlClient.MySqlCommand.ExecuteNonQuery()
в Model.MySQL.Insert() в ...\Model\MySQL.cs:строка 152

Код подключения к БД:

 public MySQL(string host, int port, string database, string username, string password)
 {
     connection = ConnectionSetting(host, port, database, username, password);
 }

 private MySqlConnection ConnectionSetting(string host, int port, string database, string username, string password)
 {
     try
     {
        string connString = "Server=" + host + ";Database=" + database + ";port=" + port + ";User Id=" + username + ";password=" + password + ";CharSet=utf8";
        MySqlConnection connection = new MySqlConnection(connString);

        return connection;
     }
     catch(Exception ex)
     {
        Logger.Error(ex, "Ошибка программы: MySQL.Connect");
        return null;
     }
 }

 public void Connect()
 {
     try
     {
        if (connection == null) return;

        connection.Open();
        connected = connection.State == System.Data.ConnectionState.Open;
     }
     catch (Exception ex)
     {
        Logger.Error(ex, "Ошибка программы: MySQL.Open");
        Disconnect();
     }
 }

Код куда ссылается ошибка:

try
{
    if (connection == null || connection.State != System.Data.ConnectionState.Open)
    {
        cmdResult = false; UpdateCommand?.Invoke();
        return;
    }

    using(MySqlCommand cmd = new MySqlCommand())
    {
        cmd.Connection = connection;
        cmd.CommandTimeout = 10_000;
        cmd.CommandText = "INSERT INTO data_table (Date, Time, User, IDDEV, PhotoID, Weight, Width, Height, Length, Volume, WSStatusID, WSErrorID, StatusDescriptionWS, BC1, BC2, BC3, BC4, BC5, BC6, BC7, BC8, BC9, BC10, BC11, BC12, BC13, BC14, BC15, BC16, WBNumber, Packages, MeasuredPacks) " +
                          "VALUES (CURDATE(), CURTIME(), @User, @IDDEV, @PhotoID, @Weight, @Width, @Height, @Length, @Volume, @WSStatusID, @WSErrorID, @StatusDescriptionWS, @BC1, @BC2, @BC3, @BC4, @BC5, @BC6, @BC7, @BC8, @BC9, @BC10, @BC11, @BC12, @BC13, @BC14, @BC15, @BC16, @WBNumber, @Packages, @MeasuredPacks)";

        cmd.Parameters.AddWithValue("@User", 0);
        cmd.Parameters.AddWithValue("@IDDEV", 0);
        cmd.Parameters.AddWithValue("@PhotoID", 0);

        cmd.Parameters.AddWithValue("@Weight", 0.0);
        cmd.Parameters.AddWithValue("@Width", 0.0);
        cmd.Parameters.AddWithValue("@Height", 0.0);
        cmd.Parameters.AddWithValue("@Length", 0.0);
        cmd.Parameters.AddWithValue("@Volume", 0.0);

        cmd.Parameters.AddWithValue("@WSStatusID", 0);
        cmd.Parameters.AddWithValue("@WSErrorID", 0);
        cmd.Parameters.AddWithValue("@StatusDescriptionWS", "");

        cmd.Parameters.AddWithValue("@BC1", "");
        cmd.Parameters.AddWithValue("@BC2", "");
        cmd.Parameters.AddWithValue("@BC3", "");
        cmd.Parameters.AddWithValue("@BC4", "");
        cmd.Parameters.AddWithValue("@BC5", "");
        cmd.Parameters.AddWithValue("@BC6", "");
        cmd.Parameters.AddWithValue("@BC7", "");
        cmd.Parameters.AddWithValue("@BC8", "");
        cmd.Parameters.AddWithValue("@BC9", "");
        cmd.Parameters.AddWithValue("@BC10", "");
        cmd.Parameters.AddWithValue("@BC11", "");
        cmd.Parameters.AddWithValue("@BC12", "");
        cmd.Parameters.AddWithValue("@BC13", "");
        cmd.Parameters.AddWithValue("@BC14", "");
        cmd.Parameters.AddWithValue("@BC15", "");
        cmd.Parameters.AddWithValue("@BC16", "");

        cmd.Parameters.AddWithValue("@WBNumber", "");
        cmd.Parameters.AddWithValue("@Packages", 0);
        cmd.Parameters.AddWithValue("@MeasuredPacks", 0);

        // Ошибка ссылается на cmd.ExecuteNonQuery()!!!
        int res = cmd.ExecuteNonQuery();

        if (res == 1)
        {
            id = Convert.ToUInt32(cmd.LastInsertedId);
            cmdResult = true;
            UpdateCommand?.Invoke();
        }
        else
        {
            id = Convert.ToUInt32(cmd.LastInsertedId);
            cmdResult = false;
            UpdateCommand?.Invoke();
        }
    } 
}
catch(Exception ex)
{
    Logger.Error(ex, "Ошибка программы MySQL.Insert");
}

Подскажите, пожалуйста, в чем может быть проблема?


Ответы (1 шт):

Автор решения: aepot

Несмотря на то что я бы все-таки переписал этот код, во-первых на асинхронный, во-вторых избавился бы от .AddWithValue, он всё-таки выглядит нормально.

Соединение на самом деле может рваться по куче причин, даже если база находится в локальном доступе, в частности если подключение к БД ведётся через TCP протокол (альтенатива - UNIX сокет). У TCP есть таймауты, могут быть потери пакетов, где при при неудачной попытке восстановления состояния соединения происходит обрыв связи. В этом случае сбой нельзя считать критическим, а при возникновении исключений с причиной такого рода, следует просто переподключиться к БД.

С другой стороны держать постоянное подключение к БД на уровне приложения вообще не следует. То есть на каждый запрос нужно поднять подключение и после выполнения запроса его закрыть. Это может показаться не эффективным, и это действительно неэффективно, если не используется пул подключений.

Другими словами, вам нужно сделать 2 вещи:

  • Включить пул подключений, добавить в строку подключения аргумент Pooling=true;
  • Создавать новый MySqlConnection на каждый запрос

Например реализовать фабрику подключений для оптимальной обработки

public class MySqlConnectionFactory
{
    private readonly string _connString;

    public MySqlConnectionFactory(string host, int port, string database, string username, string password)
    {
        _connString = $"Server={host};Database={database};port={port};User Id={username};password={password};CharSet=utf8;Pooling=true;";
    }

    public MySqlConnection CreateConnection()
    {
        var connection = new MySqlConnection(_connString);
        connection.Open();
        return connection;
    }
}

Добавьте логирование и обработку ошибок по вкусу.

Создайте один экземпляр фабрики на всё время работы с БД.

factory = new MySqlConnectionFactory(...);

И далее используйте его для создания подключений.

using MySqlConnection connection = factory.CreateConnection();
using MySqlCommand command = connection.CreateCommand();
command.CommandText = ...;

В этом случае, если нужно выполнить последовательно несколько запросов в БД, то можно и через одно подключение это сделать.

Ещё такой подход даёт приятный бонус. При использовании отдельных экземпляров MySqlConnection допускается работать с БД в многопоточном или асинхронном режиме, то есть выполнять множество запросов к БД одновременно.

→ Ссылка