Синхронизация подключение ODBC


Поскольку я осмеливаюсь больше и больше в многопоточности, мне сейчас нужно думать о том, как защитить свое драгоценное объекта odbcconnection от взлома в случайные моменты времени.

Технические характеристики проекта:

  • .Нетто-2.0
  • В ODBC-подключение через СУБД MySQL-разъем 3.51 для MySQL 5.0

Основная структура моего проекта выглядит так:

+ Assembly
+--+> Namespace1
   +--> Data-Layer
+--+> Namespace2
   +--> Data-Layer
...

Имеется также слой абстракции в месте с интерфейсов и реализаций, но думаю, что не важно, потому что мой вопрос про реализацию ODBC. Они также реализуют интерфейс IDisposable.

Это означает, что я передаю вокруг объекта odbcconnection, для различных классов и создания этих данных классов, если попросят, как это:

public class MainDataSource
{
    private OdbcConnection _conn = <Yes, It's initialized>;

    public Namespace1.Data CreateNamespace1Data()
    {
        return new Namespace1.Data(_conn);
    }

    public Namespace2.Data CreateNamespace2Data()
    {
        return new Namespace2.Data(_conn);
    }
}

В данные классы являются содержащие методы извлечения объектов из базы данных. Единственный интересный момент здесь заключается в том, что все данные класса проводит свой собственный OdbcCommand объектов, ни один из них не переданы в любом случае. Единственное, что они имеют в общем является источником объекта odbcconnection.

Поскольку некоторые из этих классов (как параметр класса) может быть вызван из нескольких потоков одновременно, мне нужно думать о лучшем способе блокировки и защиты. Первой мыслью было запереть целую объекта odbcconnection:

public class Data
{
    private OdbcConnection _conn;
    private OdbcCommand _cmd;

    public Data(OdbcConnection conn)
    {
        _conn = conn;

        _cmd = _conn.CreateCommand();
        _cmd.CommandText = <FromResource>;
        _cmd.Parametersblabla;
    }

    public MyDataObject GetObjects(String searchterm)
    {
        lock(_conn)
        {
            _cmd.Parameters[0].Value = searchterm;
            using(OdbcDataReader reader = _cmd.ExecuteReader())
            {
                // Do stuff here and return MyDataObject
            }
        }
    }
}

Это кажется немного слишком жесткие для меня, поэтому я думал только о блокировке OdbcCommand:

public class Data
{
    private OdbcCommand _cmd;

    public Data(OdbcConnection conn)
    {
        _cmd = conn.CreateCommand();
        _cmd.CommandText = <FromResource>;
        _cmd.Parametersblabla;
    }

    public MyDataObject GetObjects(String searchterm)
    {
        lock(_cmd)
        {
            _cmd.Parameters[0].Value = searchterm;
            using(OdbcDataReader reader = _cmd.ExecuteReader())
            {
                // Do stuff here and return MyDataObject
            }
        }
    }
}

Но я все еще чувствую, что я пропустил что-то очень важное здесь. Есть ли другие варианты? Я пропустил что-то важное?



3128
5
задан 19 июля 2011 в 10:07 Источник Поделиться
Комментарии
2 ответа

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

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


Ошибка [HY000] [в MySQL][драйвер ODBC 3.51 команды]из синхронизации; вы не можете запустить эту команду

Водитель в замешательстве, потому что последовательность была прервана. Это может не произойти только по OdbcCommand уровне, но на объекта odbcconnection уровне. То объекта odbcconnection, должно быть заблокировано.

Еще обратите внимание, что вы должны запереть объекта odbcconnection момент, когда вы начинаете работать с командой:

_cmd.Parameters[0].Value = argument;
lock(_conn)
{
_cmd.ExecuteNonQuery();
}

Это плохо, потому что это могло произойти в многопоточной среде, что параметр получает переопределен другим потоком, прежде чем метод executenonquery можно назвать.

lock(_conn)
{
_cmd.Parameters[0].Value = argument;
_cmd.ExecuteNonQuery();
}

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

Дальнейшие исследования показали, что MySQL и ODBC-разъем имеет механизм блокировки...но только на уровне высказывания:


Разъем MySQL через ODBC, 5.1.9, выполнить.с

 39:    SQLRETURN do_query(STMT FAR *stmt,char *query, SQLULEN query_length)
40: {
...
55: MYLOG_QUERY(stmt, query);
56: pthread_mutex_lock(&stmt->dbc->lock);
...
96: exit:
97: pthread_mutex_unlock(&stmt->dbc->lock);
...
114: }

И чтобы было понятно: если у вас не потокобезопасный связи, заблокировать всю связь.

И чтобы сделать ее более четкой, используя odbcdatareader выполняет считается использование подключения. Пока ты работаешь с odbcdatareader выполняет, вы должны держать замок на подключение.

Это включает в себя утилизация ресурсов, Ф.е. объект DataReader. Вы должны всегда использовать , используя внутри замкаЭд частей, чтобы убедиться, что ресурсы освобождаются снова, в противном случае может случиться, что объект DataReader задерживается и уничтожаются сборщиком мусора и довольно неподходящий момент, как это:

Thread  Action
1 Lock Connection
1 Create Statement
2 Lock Connection -> Wait
1 Prepare Statement
1 Execute Statement
1 Get Reader
1 Use Reader
1 Unlock Connection
2 Lock Connection
2 Prepare Statement
2 Execute Statement
GC Destroy Statement from thread #1
GC Destroy Reader from thread #1

В нем код будет такой:

lock (connection)
{
IDbCommand command = connection.CreateCommand();
command.CommandText = "SQL";
IDataReader reader = command.ExecuteReader();
// Do something the reader
return valuesFromTheReader;
}

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


Попытка чтения или записи в защищенную память.

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

Лучший шаблон-это всегда уничтожить все, а в Внутри замка:

lock (connection)
{
using (IDbCommand command = connection.CreateCommand())
{
command.CommandText = "SQL";
using (IDataReader reader = command.ExecuteReader())
{
// Do something the reader
return valuesFromTheReader;
}
}
}


1: я не шучу, я мог бы надежно воспроизвести зависание на следующей инструкцией внутри системы.Данных.В ODBC.Odbcdatareader выполняет.NextResult(буль, буль)

IL_0099: ldarg.1
00000099 8B F8 mov edi,eax

когда я не правильно распорядиться объект DataReader.

6
ответ дан 16 ноября 2011 в 10:11 Источник Поделиться

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

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

Это также имеет то преимущество, что несколько потоков могут выполнять доступ к данным в то же время, в противном случае может быть ограничение на пользу вашего многопоточность.

1
ответ дан 22 марта 2013 в 07:03 Источник Поделиться