Hallo,
Hoewel ik de afgelopen maand een website gebouwd heb (die sinds een week operationeel is) met redelijk wat php en een MySQL database, wil ik mezelf nog als een redelijke beginner beschouwen op dit gebied.
Nu zou ik de site willen uitbreiden met het transactie-principe. Dus: als gebruiker A een record leest om te updaten, dan kan gebruiker B niet tegelijkertijd hetzelfde record elzen om te updaten. Maar tot nu toe lukt het me niet. In een kleine testsituatie doe ik het volgende:
- tabel met InnoDB
- in 1 webpagina:
connect de mysql server
selecteer de database
set autocommit=0 (hoe kun je trouwens checken of dit goed gaat?)
start transaction
select * from <tabel> where id=<nummer> for update
<formulier dat waarden toont en mogelijkheid tot wijziging geeft>

Toch gaat dit niet goed: terwijl het record ingelezen is voor update, kan een andere gebruiker precies hetzelfde doen met hetzelfde record.

Zou blij zijn met tips.

bvd,
Jack

Wat je zou kunnen doen is het volgende:

Zodra gebruiker A de data ophaalt ( Selecteerd uit database ), update je een extra veld bijvoorbeeld "datum" de huidige datum en tijd ( Kan je doen dmv de functie NOW() in MySQL)

Wanneer gebruiker B dezelfde data probeerd op te halen ga je eerst kijken of er al een datum is ingevoerd + 5 mins bijvoorbeeld. Mocht dit niet zo zijn kan gebruiker B de data gewoon lezen.

Enigste nadeel hieraan is dat je nooit zeker weet wanneer gebruiker A de data niet meer leest. ( Javascript onclose is al geen optie, omdat wanneer de gebruiker Javascript uit heeft staan dit niet gaat werken )
Daarom zou je een limiet moeten stellen bijvoorbeeld 5 mins, om te kijken of gebruiker B de data mag lezen of niet.

Edit:

Voorbeeldje:
( Heb toch niks te doen atm :P )

<?php
// Wanneer een gebruiker de data probeerd te selecteren
if(isset($_GET['id']) && ctype_digit($_GET['id']))
{
$query = mysql_query("
SELECT
id
FROM
tabelnaam
WHERE
id = '".addslashes($_GET['id'])."'
AND
DATE_SUB(NOW(), INTERVAL 5 MINUTE) > datum
LIMIT 1")
or die(mysql_error());

if(mysql_num_rows($query) == 0)
{
echo "U kunt deze data momenteel niet bekijken";
}
else
{
// Hier je hele formulier wanneer iemand WEL de data mag bekijken

mysql_query("
UPDATE
tabelnaam
SET
datum = NOW()
WHERE
id = '".addslashes($_GET['id'])."'
LIMIT 1")
or die(mysql_error());
}
}
?>
Toch gaat dit niet goed: terwijl het record ingelezen is voor update, kan een andere gebruiker precies hetzelfde doen met hetzelfde record.

leesvoer

In mysql hoef je overigens niet te commiten

Het controleren of een update gelukt is kan met mysql_affected_rows()

Succes
Klaasjan Boven schreef op 27.06.2007 09:59
Toch gaat dit niet goed: terwijl het record ingelezen is voor update, kan een andere gebruiker precies hetzelfde doen met hetzelfde record.

leesvoer

In mysql hoef je overigens niet te commiten

Het controleren of een update gelukt is kan met mysql_affected_rows()

Succes


De manier die ik hierboven heb uitgelegd zou moeten werken naar mijn inzien.
Dat locktables is ook een oplossing maar dan is je hele table niet meer te gebruiken en als het nou voor 1 rij is dat niet tegelijk geedited mag worden?
Nooit zelf een functie schrijven als de DB voorziet in zoiets
Bovendien denk ik dat een row midden in een transtie sowieso gelocked is.
Volgens mij moet het kunnen met de combinatie START TRANSACTION, SELECT FOR UPDATE en COMMIT (om weer vrij te geven). Of ROLLBACK (om de transactie te annuleren). In grote datbase-systemen is dat ook zo, en volgens de MySQL syntax die ik online vond ook. Toch krijg ik het niet aan het werk.
Natuurlijk kun je met een truc werken zoals een extra veld in het record, maar dat is nou net niet de bedoeling van het transactie-concept.
Ik blijf dus nog hopen....
gebruik je InnoDB of Myisam??
Kan het zijn dat jij transacties wilt doen verdeeld over meerdere aanroepen?
-> jij vraagt pagina op
- php wordt gestart, verbinding wordt opgebouwd*
- transactie wordt gestart
- uitvoer wordt gegeven
- php stopt
-> jij vraagt 2e pagina op (bijv. met veranderingen)
- php wordt gestart, verbinding wordt opgebouwd*
- jij veronderstelt dat de transactie nog bestaat
- ...

Als dat de situatie is, dan denk ik dat het probleem zit bij de dingen die ik heb gemarkeerd met een sterretje. De verbindingen zijn dan wel gecached, de staat waarin deze verbindingen verkeren niet. En wat gebeurt er waneer een gecachte verbinding wordt gebruikt door een 3e bezoeker om iets totaal anders uit te lezen? Komt die query ook terecht in de transactie? Lijkt mij niet. Dus ik denk niet dat transacties over meerdere aanroepen kunnen blijven bestaan op deze manier.
Dank voor jullie antwoorden.

aan Donhertog: InnoDB (stond in mijn beschrijving)

aan Jelmer: Goed punt Jelmer.
Op het moment dat de gebruiker de informatie ziet en kan wijzigen, is php inderdaad even niet aan de beurt. Betekent jouw uitleg dat de hele transactie binnen één php actie moet plaatsvinden? Als dat zo is, dan kan wat ik wil misschien helemaal niet?

Dan zou ik in het record een soort update-sequence-nummer (USN) moeten bijhouden, na invoer van de wijziging het record opnieuw lezen en checken of het USN nog hetzelfde is. Zo ja, update doorvoeren, zo nee, opnieuw naar de gebruiker met de nieuwste gegevens.
In dat geval heb ik helemaal geen InnoDB en geen locking nodig, want eigenlijk bouw ik mijn eigen transactie-concept. En de kans dat iemand anders (andere transactie) in die fractie van een seconde tegelijk hetzelfde record wil wijzigen is vrijwel uitgesloten.

Graag jouw visie Jelmer.
Ik zou het zo doen :
pagina1.php

<?php
//we gaan er van uit dat hier je mysql_connect(); en je mysql_select_db(); inzit
include('connect.php');
//We sluiten de database af
mysql_query('UPDATE weetikveelwa SET close="1", time="'.time().'" WHERE id="jeid"');
//je voert alles uit
?>

verwerk_pagina1.php

<?php
//Je verwerkt je script
//lala, verwerk verwerk

//we openen de row weer
mysql_query('UPDATE weetikveelwa SET close="0" WHERE id="jeid"');
//Nu is het weer open
?>

opvragen.php

<?php
//het kan natuurlijk ook altijd dat je admin het updaten vergeten is, of dat hij het uiteindelijk toch niet wou aanpassen. daarom hebben we time nodig. we geven hem 500 seconden
mysql_query('UPDATE weetikveelwa SET close="0", time="", WHERE time < "'.time()-500'."');
//je vraagt alles op waar je close = 0, dus alles wat open is vraag je op
mysql_query('SELECT * FROM weetikveelwa WHERE close="0"');
?>

Nu kun je alles zien die de laatste 5 minuten niet aangepast zijn
Bedankt Bart.
Toch maar zelf regelen dus. Bedankt voor de voorbeelden. Voor de database waarom het hier gaat kan ik daar wel wat mee. Zal eens een prototiepje bouwen.
Dank, groet,
Jack

Reageren