Остановки при извлечении данных запроса FB1.5 + IBX 6.08 + C++Builder 6

GrafitIzh
Дата: 10.05.2012 16:48:25
Есть таблица с значениями датчиков, 4 колонки:
IDSENSORFACT - первичный ключ, bigint
FACTDATE - дата показания, timestamp
SENSORNUM - номер датчика, int
FACTVALUE - показание датчика, float

В таблице созданы прямой и обратный индексы по полям IDSensorFact и FactDate.

Нужно извлечь все показания датчиков в некотором временном интервале (одну сессию).
Программа написана на C++Builder 6, использую компоненты IBX, действую таким способом:
1. При старте программы создаю IBDatabase и IBTransaction, IBSQL
Параметры транзакции: read_committed, rec_version, nowait
2. По запросу пользователя, узнаю сколько всего будет записей в сессии отдельным запросом, устанавливаю нужный текст запроса в IBSQL и выполняю ExecQuery.
3. В таймере вынимаю по 200 записей из запроса с помощью IBSQL->Next, перерабатываю эти данные и отображаю процент выполнения пользователю

Текст программы с заполнением запроса:
        MsrDatabase->ActSQL->SQL->Add ("SELECT");
        MsrDatabase->ActSQL->SQL->Add ("IDSensorFact, FactDate, SensorNum, FactValue");
        MsrDatabase->ActSQL->SQL->Add ("FROM SensorFacts");
        MsrDatabase->ActSQL->SQL->Add ("WHERE (IDSensorFact >= " + IntToStr(ActIDFact) + ")");
        if (double (SessionProps->EndDate)){
            MsrDatabase->ActSQL->SQL->Add ("AND (FactDate <= '" + SessionProps->EndDate.FormatString("yyyy-mm-dd hh:nn:ss") + "')");
        }
        MsrDatabase->ActSQL->SQL->Add ("ORDER BY IDSensorFact");


На практике выходит так:
1. ExecQuery проходит быстро, первые секунд 15 выборка данных из запроса в таймере идет хорошо, сервер firebird загружает почти целиком одно ядро процессора,
2. потом все замирает: firebird освобождает процессор, моя программа перестает реагировать на действия пользователя (подвисает).
3. далее выборка данных продолжается очередные 15 секунд, потом опять подвисание... и так по циклу.

Путем прогона программы в пошаговом режиме выяснил, что зависает именно при вызове метода IBSql->Next, но не каждый раз.
Добавил в программе логирование в текстовый файл, откуда выяснил, что запрос извлекается порциями по 2520 записей, иногда по 1260 записей, зависания длятся ровно по 60 секунд.
В ibexpert запрос проходит без проблем, когда смотрю статистику запроса, то вижу что Indexed reads = 2520.

Вопрос знатокам: как исправить ситуацию, чтобы соединение не подвисало? Укажите направление куда копать.
Dimitry Sibiryakov
Дата: 10.05.2012 16:59:25

GrafitIzh
как исправить ситуацию, чтобы соединение не подвисало?

Таймер выкинь. Используй многопоточность.

Posted via ActualForum NNTP Server 1.5

GrafitIzh
Дата: 10.05.2012 17:04:01
Таймер нужен чтобы показывать пользователю процент выполнения загрузки данных,
чтобы можно было ее отменить, и много еще для чего,
в программе реализован режим закачки "живых" данных, когда они добавляются в таблицу в реал-тайм,
поэтому выкинуть таймер - не вариант.
Dimitry Sibiryakov
Дата: 10.05.2012 17:05:35

GrafitIzh
Таймер нужен чтобы показывать пользователю процент выполнения загрузки данных, чтобы можно
было ее отменить, и много еще для чего,

Всё то же самое делается в потоках гораздо приятнее.

Posted via ActualForum NNTP Server 1.5

GrafitIzh
Дата: 10.05.2012 17:26:48
Спасибо за направление движения, но к таким радикальным изменения в программе я пока не готов.

Может есть идей как остаться при таймере и уйти от зависаний?
Да и не факт что дело именно в таймере:
в этой же проге реализована такая же схема извлечения данных из другой базы, тоже с таймером, и работает..
с другой стороны, в ibexpert запрос извлекается на ура.
kdv
Дата: 10.05.2012 18:14:48
GrafitIzh
3. далее выборка данных продолжается очередные 15 секунд, потом опять подвисание... и так по циклу.

выполни свой запрос в IBExpert, в SQL Editor через fetch all.

Вообще у тебя (и так ясно) запрос выполняется с планом SensorFacts order IDSensorFact, что значит выборка в порядке индекса. А в этом случае идет высокий i/o в отношении страниц данных. т.е. ФБ вытаскивает порцию, потом еще порцию, и т.д. А раз зависания по 60 секунд на несчастных 2000 записей, то значит и проц хилый, и диск.

GrafitIzh
когда смотрю статистику запроса, то вижу что Indexed reads = 2520.

тебе не туда смотреть надо.

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

p.s. см.
http://ibaseforum.ru/viewtopic.php?f=4&t=4175
GrafitIzh
Дата: 11.05.2012 09:37:09
Систему хилой не назовешь, по крайней мере для таких задач:
Процессор Intel Pentium D 3.40 Ггц, двухядерный
Диск Sata, 7200 rpm, марку не помню, думаю не важно
Оперативка 2 Гб

kdv
выполни свой запрос в IBExpert, в SQL Editor через fetch all


Выполнил, запрос выбирает 135269 строк:

Plan
PLAN (SENSORFACTS ORDER PK_SENSORFACTS)

Adapted Plan
PLAN (SENSORFACTS ORDER PK_SENSORFACTS)

------ Performance info ------
Prepare time = 0ms
Execute time = 29s 766ms
Avg fetch time = 0,22 ms
Current memory = 3 513 880
Max memory = 3 609 440
Memory buffers = 2 048
Reads from disk to cache = 64 035
Writes from cache to disk = 0

Насколько я понимаю, раз в ibexpert запрос проходит, а в программе нет, то виновата программа.
Но где именно затык, в компонентах IBX, в таймерных прерываниях, или в моей голове бардак, или еще в чем - не понимаю, и поэтому прошу помощи, вдруг кто сталкивался с подобным случаем.
GrafitIzh
Дата: 11.05.2012 09:53:12
Снес антивирус, эффекта это не дало, он ни при чем.
Микросекунда
Дата: 11.05.2012 09:53:24
GrafitIzh
Путем прогона программы в пошаговом режиме выяснил, что зависает именно при вызове метода IBSql->Next, но не каждый раз.


Покажи это место

Posted via ActualForum NNTP Server 1.5

GrafitIzh
Дата: 11.05.2012 10:18:15
Микросекунда
Покажи это место


Ок, даю более полное описание проги.
Программа работает с двумя БД на firebird. Первая БД - на внешней машине с показаниями датчиков, вторая БД - на локальной машине (коннект через 127.0.0.1). Суть актуализации данных в том чтобы снять данные из первой БД и положить во вторую БД по запросу пользователя. Данные привязаны к временному интервалу (сессии), какую сессию актуализировать решает пользователь.

В "лабораторных" условиях первая и вторая БД обе лежат на одной машине, т.е. один сервер firebird обслуживает 2 базы.
Первая БД обслуживается классом TMonDatabase, вторая БД обслуживается классом TMsrDatabase.

Как действует программа:
1. При старте программы создаю IBDatabase и IBTransaction, IBSQL
Параметры транзакции: read_committed, rec_version, nowait
2. При старте актуализации вызываю метод TMonDatabase::StartActualization, а также TMonDatabase::GetActRecordsRemained чтобы узнать сколько записей всего выберет запрос.
3. В таймере вызываю метод TMonDatabase::GetNextActualData, с помощью чего вынимаю по 200 записей из запроса, перерабатываю их, складываю во вторую БД

Код программы:
+
bool TMonDatabase::StartActualization ()
{
    ActPrepared = true;
    MsrDatabase->RetrieveStages (SessionProps->IDSession);
    ActIDFact = -1;
    TIBQuery *Q = new TIBQuery (NULL);
    bool Result = false;
    bool Ok = false;
    Q->Database = MsrDatabase->IBDatabase;
    Q->Transaction = MsrDatabase->IBTransaction;
    try
    {
        MsrDatabase->IBTransaction->Active = true;
        Q->SQL->Add ("SELECT FIRST 1 IDSensorFact");
        Q->SQL->Add ("FROM SensorFacts");
        Q->SQL->Add ("WHERE FactDate >= :StartDate");
        Q->SQL->Add ("ORDER BY IDSensorFact;");
        Q->Params->Items[0]->AsDateTime = SessionProps->StartDate;
        Q->Active = true;
        Q->FetchAll ();
        Q->First ();
        if (!Q->FieldByName ("IDSensorFact")->IsNull)
        {
            ActIDFact = Q->FieldByName ("IDSensorFact")->AsInteger;
            Result = true;
        }
        else
        {
            Q->SQL->Clear ();
            Q->SQL->Add ("SELECT MAX (IDSensorFact) MaxIDSensorFact");
            Q->SQL->Add ("FROM SensorFacts;");
            Q->Active = true;
            Q->FetchAll ();
            Q->First ();
            if (!Q->FieldByName ("MaxIDSensorFact")->IsNull)
            {
                ActIDFact = Q->FieldByName ("MaxIDSensorFact")->AsInteger;
                Result = true;
            }
        }

        MsrDatabase->ActTransaction->Active = true;
        MsrDatabase->ActSQL->SQL->Clear();
        MsrDatabase->ActSQL->SQL->Add ("SELECT");
        MsrDatabase->ActSQL->SQL->Add ("IDSensorFact, FactDate, SensorNum, FactValue");
        MsrDatabase->ActSQL->SQL->Add ("FROM SensorFacts");
        MsrDatabase->ActSQL->SQL->Add ("WHERE (IDSensorFact >= " + IntToStr(ActIDFact) + ")");
        if (double (SessionProps->EndDate)){
            MsrDatabase->ActSQL->SQL->Add ("AND (FactDate <= '" + SessionProps->EndDate.FormatString("yyyy-mm-dd hh:nn:ss") + "')");
        }
        MsrDatabase->ActSQL->SQL->Add ("ORDER BY IDSensorFact");
        MsrDatabase->ActSQL->ExecQuery();

        Ok = true;
    }
    __finally
    {
        if (Ok) MsrDatabase->IBTransaction->Commit ();
        else MsrDatabase->IBTransaction->Rollback ();
    }
    if (Q) delete (Q);
    return Result;
}

int TMonDatabase::GetActRecordsRemained ()
{
    TIBQuery *Q = new TIBQuery (NULL);
    bool Result = false;
    bool Ok = false;
    int ActIDLastFact;
    Q->Database = MsrDatabase->IBDatabase;
    Q->Transaction = MsrDatabase->IBTransaction;
    try
    {
        MsrDatabase->IBTransaction->Active = true;
        Q->SQL->Add ("SELECT FIRST 1 IDSensorFact");
        Q->SQL->Add ("FROM SensorFacts");
        if (double (SessionProps->EndDate)){
            Q->SQL->Add ("WHERE FactDate <= :EndDate");
            Q->Params->Items[0]->AsDateTime = SessionProps->EndDate;
        }
        Q->SQL->Add ("ORDER BY IDSensorFact DESC;");

        Q->Active = true;
        Q->FetchAll ();
        Q->First ();
        if (!Q->FieldByName ("IDSensorFact")->IsNull)
        {
            ActIDLastFact = Q->FieldByName ("IDSensorFact")->AsInteger;
            Result = true;
        }
        Ok = true;
    }
    __finally
    {
        if (Ok) MsrDatabase->IBTransaction->Commit ();
        else MsrDatabase->IBTransaction->Rollback ();
    }
    if (Q) delete (Q);
    if (Result) return ActIDLastFact - ActIDFact + 1;
    else return 0;
}

int TMonDatabase::GetNextActualData (int RecordCount)
{

    if (!ActPrepared) return 0;
    TIBQuery *Q2 = new TIBQuery (NULL);
    bool Ok = false;
    int ActIDLastFact;
    int NewIDFact;
    int Index;
    TDateTime NewFactDate;
    int NewSensorNum;
    double NewFactValue;
    Q2->Database = IBDatabase;
    Q2->Transaction = IBTransaction;
    int RecordsRead = 0;
    try
    {
        IBTransaction->Active = true;
        while (!MsrDatabase->ActSQL->Eof && (RecordsRead < RecordCount))
        {
            NewIDFact = MsrDatabase->ActSQL->FieldByName ("IDSensorFact")->AsInteger;
            NewFactDate = MsrDatabase->ActSQL->FieldByName ("FactDate")->AsDateTime; // &#238;&#234;&#240;&#243;&#227;&#235;&#255;&#229;&#242; &#236;&#232;&#235;&#235;&#232;&#241;&#229;&#234;&#243;&#237;&#228;&#251;!
            NewSensorNum = MsrDatabase->ActSQL->FieldByName ("SensorNum")->AsInteger;
            NewFactValue = MsrDatabase->ActSQL->FieldByName ("FactValue")->AsFloat;
            Index = SensorList->GetSensorIndex (NewSensorNum);
            Q2->SQL->Clear ();
            Q2->SQL->Add ("INSERT INTO DocSensorValues (IDSensorValue, IDDocSensor, DateTime, SensorValue)");
            Q2->SQL->Add ("VALUES (GEN_ID (Gen_DocSensorValues_ID, 1), :IDDocSensor, :DateTime, :SensorValue);");
            Q2->Params->Items[0]->AsInteger = SensorList->GetIDSensor (Index);
            Q2->Params->Items[1]->AsDateTime = NewFactDate;
            Q2->Params->Items[2]->AsFloat = NewFactValue;
            SensorList->SetSensor (Index, NewFactDate, NewFactValue);
            frmGraphics->PutData (Index, NewFactDate, NewFactValue);
            Q2->ExecSQL ();
            RecordsRead++;
            ActIDFact = NewIDFact + 1;

            AnsiString S = DecodeDateTimeSmall(Now()) + '\t' + IntToStr(NewIDFact) + '\r' + '\n';
            TxtFile->Write(S.c_str(), S.Length()*sizeof(char));
            MsrDatabase->ActSQL->Next ();
        }

        if (MsrDatabase->ActSQL->Eof){
            MsrDatabase->ActSQL->Close();
            MsrDatabase->ActSQL->SQL->Clear();
            MsrDatabase->ActSQL->SQL->Add ("SELECT");
            MsrDatabase->ActSQL->SQL->Add ("IDSensorFact, FactDate, SensorNum, FactValue");
            MsrDatabase->ActSQL->SQL->Add ("FROM SensorFacts");
            MsrDatabase->ActSQL->SQL->Add ("WHERE (IDSensorFact >= " + IntToStr(ActIDFact) + ")");
            if (double (SessionProps->EndDate)){
                MsrDatabase->ActSQL->SQL->Add ("AND (FactDate <= '" + SessionProps->EndDate.FormatString("yyyy-mm-dd hh:nn:ss") + "')");
            }
            MsrDatabase->ActSQL->SQL->Add ("ORDER BY IDSensorFact;");
            MsrDatabase->ActSQL->ExecQuery();
        }

        Ok = true;
    }
    __finally
    {
        if (Ok)
        {
            IBTransaction->Commit ();
        }
        else
        {
            IBTransaction->Rollback ();
        }
    }
    if (Q2) delete (Q2);
    if (Ok) return RecordsRead;
    else return 0;
}