SQLite egy táblába írás egy időben, két szálból

SQLite egy táblába írás egy időben, két szálból
2016-06-09T14:52:59+02:00
2016-06-17T18:25:21+02:00
2022-12-03T19:15:38+01:00
wsx
Igazából ez inkább SQLite kérdés lenne, de nem találtam megfelelő kategóriát. Azt olvastam, hogy az SQLite egy insert, vagy update parancsok esetében az egész táblát lock-olja, nem csak azt a rekordot, amellyel éppen dolgozik. 

Van egy Android alkalmazásom, amit Delphi-ben készítettem, és SQLite adatbázisba ment koordinátákat. A mentés után, minden második koordináta után, a korábban beírt, de nem szinkronizált koordinátákat egy MySQL adatbázisba írom / írnám bele. 
A locationsernsor onlocationchange eseményében azonnal megtörténik az adott koordináta beírása az SQLite adatbázis táblába, majd ezután egy task-ot indítva történik meg a még nem szinkronizált koordnáták beírása a MySQL adatbázisba. Egy flag-el (Y/N) jelölöm az SQLite adatbázisban azokat a rekordokat, amelyek még nem, vagy már szinkronizálódtak. 

1, Lekérdezem az SQLite-ből azokat a rekordokat, ahol a flag = 'N';
2, Elkezdek a lekérdezett rekordokon végigmenni
3, Az adott redkord adatait beírom a MySQL adatbázis táblába
4, A flag-et 'Y'-re állítom
Mindez tranzakcióban van kezelve. A probléma akkor jön, amikor a fenti futás még tart, és jön egy újabb onlocationchange esemény, amely beírja az új koordinátát az SQLite adatábziba, mert azt vettem észre, hogy ilyen esetben a flag nem áll át 'Y'-re, és emiatt a MySQL-be való íráskor kivétel keletkezik, mert bár a MySQL táblába a rekord beírodott, de mivel az SQLite adatbázisba a flag nem állt 'Y'-re, ezért ugyan azt a rekordot akrja kétszer beírni a MySQL adatbázisba, ami nem fog menni egyedi kulcs hiba/kivétel keletkezése miatt. Emiatt aztán tovább már a szinkronizáció nem is megy, hanem ugyan azt a rekordot, amit persze nem tud, probálgatja beírni a MySQL adatbázis táblába, és keletkezik egymás után a kivétel.

Van valakinek ötlete erre a fajta szinkronizálás megoldására?
Mutasd a teljes hozzászólást!
Mivel jobb ötletet nem találtam/kaptam, alternatívaként beiktattam egy ellenőrzés. Ha a MySQL táblában már létzik az a rekord, amit be szeretnék írni, akkor a beírást nem végzem el, csak az SQLite adatábzisban a flag átállítását, hogy az adott rekord szinkronizálva van.
Mutasd a teljes hozzászólást!

  • Nem világos, hogy most több szálad, vagy több processzed van, de épp tegnap gondolkodtam rajta, hogy kéne-e SQLite-ot használnom, ezért még nyitva van: SQLite Frequently Asked Questions (illetve az alatta levő)
    Szóval ha SQLITE_BUSY-t kapsz, akkor azt kezelni kéne és/vagy meg kéne nézni, hogy szálbiztos-e az adott implementáció (bár valószínűleg az)

    Ennek a kétadatbázisos egymásbólmásolgatásnak pontosan mi értelme van?
    Mutasd a teljes hozzászólást!
  • procedure TAndroidServiceDM.LocationSensor1LocationChanged(Sender: TObject; const OldLocation, NewLocation: TLocationCoord2D); begin try Log('New location available -> Latitude: %2.6f, Longitude: %2.6f ', [NewLocation.Latitude, NewLocation.Longitude]); insertrow(newlocation.Latitude,newlocation.Longitude); inc(countCoordinates); if (countCoordinates>=2) then begin if (synctask <> nil) then begin if (synctask.Status = ttaskstatus.Completed) then freeandnil(synctask); end; if (syncTask = nil) then Begin syncTask := ttask.Create(SyncMysqlDatabase); End; if (syncTask.Status <> ttaskstatus.Completed) then begin logi('syncTask.Start'); syncTask.Start; end; end; except on e:exception do begin log('LocationSensor1LocationChanged kivetel: '+e.Message,[]); end; end; end; procedure TAndroidServiceDM.SyncMysqlDatabase; begin logi('syncmysqldatabase start'); try IdTCPClient1.Connect; if (IdTCPClient1.Connected) then begin SyncCoordinates; countCoordinates := 0; IdTCPClient1.Disconnect; end; except on e:exception do begin IdTCPClient1.Disconnect; log(e.Message,[]); end; end; logi('syncmysqldatabase finished'); end; procedure TAndroidServiceDM.SyncCoordinates; begin DataSnapConnection.Connected := True; //Datasnap szerver kapcsolat if DataSnapConnection.Connected then begin log('querySyncWaitCoodrinates start',[]); querySyncWaitCoodrinates.Close; try querySyncWaitCoodrinates.open; log('querySyncWaitCoodrinates.open',[]); while not (querySyncWaitCoodrinates.Eof) do begin try if DataSnapConnection.InTransaction = false then //Datasnap DataSnapConnection.BeginTransaction; cdsInsertTrackinginfo.ParamByName('tidatetime').asstring := querySyncWaitCoodrinates.FieldByName('tidatettime').asstring; cdsInsertTrackinginfo.ParamByName('tiphonenumber').asstring := UsePhoneNumber; cdsInsertTrackinginfo.ParamByName('tilatitude').asfloat := querySyncWaitCoodrinates.FieldByName('tilatitude').asfloat; cdsInsertTrackinginfo.ParamByName('tilongitude').asfloat := querySyncWaitCoodrinates.FieldByName('tilongitude').asfloat; cdsInsertTrackinginfo.Execute; log('mysql szerverre iras:'+cdsInsertTrackinginfo.CommandText,[]); if SQLiteConnection.InTransaction = false then //SQLite SQLiteConnection.BeginTransaction; qSyncFlagUpdate.Prepared := true; qSyncFlagUpdate.ParamByName('ptisync').AsString := 'Y'; qSyncFlagUpdate.ParamByName('pphonenumber').AsString := UsePhoneNumber; qSyncFlagUpdate.ParamByName('ptidatetime').asstring := querySyncWaitCoodrinates.FieldByName('tidatettime').asstring; qSyncFlagUpdate.ExecSQL(false); log('sqlite rekord megjelolese - Y',[]); if SQLiteConnection.InTransaction then SQLiteConnection.Commit; if DataSnapConnection.InTransaction then DataSnapConnection.Commit; querySyncWaitCoodrinates.Next; except on e:exception do begin log('SyncCoordinates kivetel tortent: '+e.Message,[]); SQLiteConnection.Rollback; DataSnapConnection.Rollback; end; end; end; except on e:exception do begin querySyncWaitCoodrinates.Close; log(e.Message,[]); end; end; log('querySyncWaitCoodrinates end',[]); end;end;

    06-08 19:46:12.064: I/info(21955): SyncCoordinates kivetel tortent: Remote error: Duplicate entry '2016-05-18 16:31:02-06702113051' for key 'PRIMARY'

    Ezt az üzenetet kapom, mert miközben megy a MySQL adatbázis szinkronizálás, és történik egy új koordináta beírás, mert lépett kettőt a felhasználó, akkor már a flag nem áll át 'Y'-re, és ugyan azt a rekordot akrja a MySQL-be beírni, ami már egyszer benne van. 

    Azért kell szinkronizálni, hogy egy másik készülékről be lehessen tölteni a koordinátákat, és meg lehetssen nézni, hogy a felhasználó merre járkált. Ne haragudj, de nem írom le az ötletemet, mert akkor majd valaki más megcsinálja. A lényeg, hogy úgy tűnik, hogy konkukrens írást egy időben nem lehet csinálni SQLite-al, mert nem a rekordot lock-olja, hanem az egész táblát, és abban a pillanatban, amikor egy új koordinátát, és egy szinrkon műveletet akarna csinálni, egy időben, akkor a szinkron műveletnél lévő flag nem update-elődik, és emiatt újra, és újra ugyan azt a rekordot akarja a MySQL táblába beírni, amit már korában egyszer beleírt.

    Erre kellene nekem egy alternatív megoldás, mert a priorítás az, hogy minden egyes onlocationchange eseménynél a koordináta beíródjon az SQLite táblába, és a második priorítás, hogy utána az adatok menjenek fel a MySQL táblába is. Mivel nem várható el a felhasználótól, hogy folyamatos internet kapcsolata legyen, ezért nem lehet azt csinálni, hogy azonnal a MySQL táblába mentse a koordinátákat. Akárcsak mondjuk egy Facebook esetében, ahol immár jó ideje tudsz mondjuk like-olni, vagy commentelni, off-line is, és aztán szinkronizálja az adatokat a szerverrel a program, amikor már van internet kapcsolat.
    Mutasd a teljes hozzászólást!
  • if (synctask <> nil) then // true begin if (synctask.Status = ttaskstatus.Completed) then // false freeandnil(synctask); end; if (syncTask = nil) then // false Begin syncTask := ttask.Create(SyncMysqlDatabase); End; if (syncTask.Status <> ttaskstatus.Completed) then // true begin logi('syncTask.Start'); syncTask.Start; // gotcha end;

    Ha a syncTask.Start egy threadpoolban, vagy újabb szálon indítja a taskot, akkor az versenyezni fog magával 2 (vagy több) szálon. Ekkor lehet, hogy a második szál előbb kérdez le, mint az első update-elt volna, így N-nek látja a rekordot, és ő is megpróbálja később beszúrni. Ráadásul csak sikeres szinkronizációkor hívsz querySyncWaitCoodrinates.Next-et, így végig azon fog pörögni.

    Szerintem a Delphi kódodban kéne megoldani, hogy ne versenyezzen a task saját magával több szálon (critical section), meg megnézni azokat az ifeket, amiket visszaidéztem.
    Mutasd a teljes hozzászólást!
  • Szia!

    Az SQLite dokumentációjában benne van, hogy lehet több szálon használni, de írni csak egy szálon szabad.

    Egy örökölt rendszernél én db-t váltottam, mert hibajelzés nélkül lefutott insert vagy update utasítást követően kiderült, hogy nincs változás a db-ben!

    Felejtsd el a flag-et, az MySQL-ben tárolt legmagasabb SQLite ID alapján szinkronizáld a rekordokat.

    BySzi
    Mutasd a teljes hozzászólást!
  • Igen, valami ilyesmit olvastam én is. Csak gondoltam, hogy erre van valami alternatív megoldás. 
    Jelenleg nem tárolok ID-t az adatbázisban.

    Az egyedi kulcs a timestamp és telefonszám együttese. 

    Ez a megoldás, amit most csinál azért is lenne jobb, mert így nem kell lekérdeznem, csak írnom a MySQL táblába. Megspórolok egy lekérdezést, ami lassíthatja a rendszert. Az SQLite-ból gyorsan lekérdezi, és update-eli a rekordokat, és a MySQL-be meg csak insert into utasítások futnak. A lehet leg kevesebb adatbázis kommunikáció miatt.
    Mutasd a teljes hozzászólást!
  • Mivel jobb ötletet nem találtam/kaptam, alternatívaként beiktattam egy ellenőrzés. Ha a MySQL táblában már létzik az a rekord, amit be szeretnék írni, akkor a beírást nem végzem el, csak az SQLite adatábzisban a flag átállítását, hogy az adott rekord szinkronizálva van.
    Mutasd a teljes hozzászólást!
Tetszett amit olvastál? Szeretnél a jövőben is értesülni a hasonló érdekességekről?
abcd