MySQL Entity Framework — истек тайм-аут подключения

#c# #mysql #entity-framework #.net-core #google-cloud-sql

# #c# #mysql #entity-framework #.net-ядро #google-cloud-sql

Вопрос:

Я использую Google Cloud SQL с MySQL версии 5.7 из приложения C # .NET-core 2.2 и entity framework 6.

В моих журналах я вижу следующее исключение из нескольких мест в коде, из которого я использую базу данных:

 MySql.Data.MySqlClient.MySqlException (0x80004005): Connect Timeout expired. ---> System.OperationCanceledException: The operation was canceled.
   at System.Threading.CancellationToken.ThrowOperationCanceledException()
   at System.Threading.SemaphoreSlim.WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at MySqlConnector.Core.ConnectionPool.GetSessionAsync(MySqlConnection connection, IOBehavior ioBehavior, CancellationToken cancellationToken) in C:projectsmysqlconnectorsrcMySqlConnectorCoreConnectionPool.cs:line 42
   at MySql.Data.MySqlClient.MySqlConnection.CreateSessionAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:projectsmysqlconnectorsrcMySqlConnectorMySql.Data.MySqlClientMySqlConnection.cs:line 507
   at MySql.Data.MySqlClient.MySqlConnection.CreateSessionAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:projectsmysqlconnectorsrcMySqlConnectorMySql.Data.MySqlClientMySqlConnection.cs:line 523
   at MySql.Data.MySqlClient.MySqlConnection.OpenAsync(Nullable`1 ioBehavior, CancellationToken cancellationToken) in C:projectsmysqlconnectorsrcMySqlConnectorMySql.Data.MySqlClientMySqlConnection.cs:line 232
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected)
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues, CancellationToken cancellationToken)
 

Это происходит временно на долю секунды, когда есть некоторая нагрузка на базу данных (не очень высокая, около 20% процессора компьютера базы данных).

Настройка контекста:

 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseMySql(
            new System.Net.NetworkCredential(string.Empty, ConfigurationManager.CacheCS).Password, builder =>
            {
                builder.EnableRetryOnFailure(15, TimeSpan.FromSeconds(30), null);
            }
            );
    }
}
 

Это устанавливает до 15 попыток и максимум 30 секунд между попытками.

Из журнала видно, что эта MySqlConnector конкретная ошибка не повторяется.

Мои попытки

Попытался добавить номера временных ошибок в список номеров ошибок для добавления:

 builder.EnableRetryOnFailure(15, TimeSpan.FromSeconds(30), MySqlErrorCodes.TransientErrors);
 

где MySqlErrorCodes.TransientErrors определяется как:

  public enum MySqlErrorCode
    {
        // Too many connections
        ConnectionCountError = 1040,
        // Unable to open connection
        UnableToConnectToHost = 1042,
        // Lock wait timeout exceeded; try restarting transaction
        LockWaitTimeout = 1205,
        // Deadlock found when trying to get lock; try restarting transaction
        LockDeadlock = 1213,
        // Transaction branch was rolled back: deadlock was detected
        XARBDeadlock = 1614

    }

    public class MySqlErrorCodes
    {
        static MySqlErrorCodes()
        {
            TransientErrors = new HashSet<int>()
            {
                (int)MySqlErrorCode.ConnectionCountError,
                (int)MySqlErrorCode.UnableToConnectToHost,
                (int)MySqlErrorCode.LockWaitTimeout,
                (int)MySqlErrorCode.LockDeadlock,
                (int)MySqlErrorCode.XARBDeadlock
            };
        }

        public static HashSet<int> TransientErrors { get; private set; }
    }
 

Это не сработало.

Вопросы

Как я могу решить эту проблему? Есть ли способ сделать Entity Framework более устойчивым к таким проблемам с подключением?

Редактировать

The issue occurs when I use this code to execute a raw sql command to call a stored procedure:

 public static async Task<RelationalDataReader> ExecuteSqlQueryAsync(this DatabaseFacade databaseFacade,
                                                     string sql,
                                                     CancellationToken cancellationToken = default(CancellationToken),
                                                     params object[] parameters)
{

    var concurrencyDetector = databaseFacade.GetService<IConcurrencyDetector>();

    using (concurrencyDetector.EnterCriticalSection())
    {
        var rawSqlCommand = databaseFacade
            .GetService<IRawSqlCommandBuilder>()
            .Build(sql, parameters);

        return await rawSqlCommand
            .RelationalCommand
            .ExecuteReaderAsync(
                databaseFacade.GetService<IRelationalConnection>(),
                parameterValues: rawSqlCommand.ParameterValues,
                cancellationToken: cancellationToken);
    }
}
 

 using (var context = new CacheDbContext())
            {
                using (var reader = await context
                    .Database
                    .ExecuteSqlQueryAsync("CALL Counter_increment2(@p0, @p1, @p2)",
                        default(CancellationToken),
                        new object[] { id, counterType, value })
                    .ConfigureAwait(false)
                    )
                {
                    reader.DbDataReader.Read();
                    if (!(reader.DbDataReader[0] is DBNull))
                        return Convert.ToInt32(reader.DbDataReader[0]);
                    else
                    {
                        Logger.Error($"Counter was not found! ('{id}, '{counterType}')");
                        return 1;
                    }
                }
            }
 

Я думаю, что это может быть причиной отсутствия попыток для тайм-аута подключения.

Как я могу повторить это безопасно, не выполняя одну и ту же хранимую процедуру дважды?

Редактировать

Это глобальные переменные:

ПОКАЗЫВАТЬ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ТИПА ‘% timeout%’

 connect_timeout 10
delayed_insert_timeout  300
have_statement_timeout  YES
innodb_flush_log_at_timeout 1
innodb_lock_wait_timeout    50
innodb_rollback_on_timeout  OFF
interactive_timeout 28800
lock_wait_timeout   31536000
net_read_timeout    30
net_write_timeout   60
rpl_semi_sync_master_async_notify_timeout   5000000
rpl_semi_sync_master_timeout    3000
rpl_stop_slave_timeout  31536000
slave_net_timeout   30
wait_timeout    28800
 

ПОКАЗЫВАТЬ ГЛОБАЛЬНЫЙ СТАТУС ТИПА ‘% timeout%’

 Ssl_default_timeout 7200
Ssl_session_cache_timeouts  0
 

ПОКАЗЫВАТЬ ГЛОБАЛЬНЫЙ СТАТУС ТИПА «% время безотказной работы%»

 Uptime  103415
Uptime_since_flush_status   103415
 

В дополнение к проблеме с тайм-аутом подключения я также вижу следующий журнал:

 MySql.Data.MySqlClient.MySqlException (0x80004005): MySQL Server rejected client certificate ---> System.IO.IOException: Unable to read data from the transport connection: Broken pipe. ---> System.Net.Sockets.SocketException: Broken pipe
 

Что, по-видимому, связано с проблемой подключения к базе данных.

Безопасно ли повторять попытку при таком исключении?

Комментарии:

1. В командной строке MySQL, пожалуйста, опубликуйте ТЕКСТОВЫЕ результаты: А) ПОКАЗАТЬ ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ, ТАКИЕ КАК ‘% timeout%; и Б) ПОКАЗАТЬ ГЛОБАЛЬНЫЙ СТАТУС, ТАКОЙ КАК ‘% timeout%; и В) ПОКАЗАТЬ ГЛОБАЛЬНЫЙ СТАТУС, ТАКОЙ КАК’% uptime%’;

2. Я думаю, что вход в систему невозможен в облачном Sql

3. Есть ли у вас способ получить доступ к командной строке MySQL для запуска SELECT NOW(); чтобы вернуть время суток? Или ПОКАЗЫВАТЬ БАЗЫ ДАННЫХ; ?

4. Если вы не можете, попросите службу поддержки Google выполнить запросы для вас и поделиться с вами результатами, пожалуйста.

5. Может ли это быть проблемой с подключением между прокси-сервером и экземпляром базы данных, не завершающим соединение должным образом?

Ответ №1:

Проблема возникла из-за низкого значения for maximumpoolsize в строке подключения.

Когда существует несколько потоков, использующих базу данных, и недостаточно подключений для обработки всех запросов, это может привести к таймауту подключения.

Чтобы исправить это, измените это в строке подключения на более высокое значение:

 Max Pool Size={maxConnections};