php-tapahtumat. Kuten käy ilmi, kaikki tietävät, mutta kaikki eivät ymmärrä. Tapahtumat mysqlissa ja SELECT FOR UPDATE

(9)

En ole löytänyt oikeaa esimerkkiä PHP-tiedostosta, joka käyttää MySQL-tapahtumia. Voitko näyttää minulle yksinkertaisen esimerkin tästä?

Ja vielä yksi kysymys. Olen jo ohjelmoinut paljon, enkä ole käyttänyt tapahtumia. Voinko laittaa PHP-funktion tai jotain header.php-tiedostoon, jos jokin mysql_query epäonnistuu, niin myös muut epäonnistuvat mysql_queryssä?

Luulen, että tajusin sen, eikö?

Mysql_query("SET AUTOCOMMIT=0"); mysql_query("ALOITA TAPAHTUMA"); $a1 = mysql_query("INSERT INTO rarara (l_id) VALUES("1")"); $a2 = mysql_query("INSERT INTO rarara (l_id) VALUES("2")"); if ($a1 ja $a2) ( mysql_query("COMMIT"); ) else ( mysql_query("ROLLBACK"); )

Vastaukset

Kun käytät PDO-liitäntää:

$pdo = new PDO("mysql:host=localhost;dbname=mydb;charset=utf8", $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // tämä on tärkeää ]);

Käytän usein seuraavaa koodia tapahtumien hallintaan:

Funktiotapahtuma(Closure $callback) ( globaali $pdo; // oletetaan, että PDO-yhteytemme on globaalissa var:ssa // aloita tapahtuma try-lohkon ulkopuolella, koska // et halua peruuttaa tapahtumaa, joka $pdo->beginTransaction(); try ( $callback(); $pdo->commit(); ) catch (poikkeus $e) // tämä on parempi korvata Throwablella PHP 7+:ssa ( $); pdo->rollBack();

Käyttöesimerkki:

Tapahtuma(funktio() ( globaali $pdo; $pdo->query("ensimmäinen kysely"); $pdo->query("toinen kysely"); $pdo->query("kolmas kysely"); ));

Näin tapahtumakoodi ei toistu projektissa. Tämä on hyvä, koska tämän säikeen muiden SAN-ratkaisujen perusteella tämä on helppo saada väärin. Yleisimmät ovat unohtaa poikkeuksen heittäminen uudelleen ja aloittaa tapahtuma try-lohkon sisällä.

Minulla oli tämä, mutta en ole varma, onko se oikein. Sitäkin voisi kokeilla.

Mysql_query("ALOITA TRANSACTION"); $lippu = tosi; $query = "INSERT INTO testing (myid) VALUES ("testi""); $query2 = "LISÄÄ testaus2 (myid2) ARVOT ("testi2")"; $tulos = mysql_query($query) tai trigger_error(mysql_error(), E_USER_ERROR); if (!$tulos) ( $lippu = false; ) $tulos = mysql_query($query2) tai trigger_error(mysql_error(), E_USER_ERROR); if (!$tulos) ( $lippu = false; ) if ($lippu) ( mysql_query("COMMIT"); ) else ( mysql_query("ROLLBACK"); )

Tarkista, mitä tallennusmoottoria käytät. Jos se on MyISAM, tapahtumaa ("COMMIT", "ROLLBACK") ei tueta, koska vain InnoDB-tapahtuma, ei MyISAM, tukee tapahtumia.

Tein funktion pyyntövektorin saamiseksi ja tapahtuman suorittamiseksi, ehkä joku pitää siitä hyödyllistä:

Funktiotapahtuma ($con, $Q)( mysqli_query($con, "START TRANSACTION"); for ($i = 0; $i< count ($Q); $i++){ if (!mysqli_query ($con, $Q[$i])){ echo "Error! Info: <" . mysqli_error ($con) . ">Kysely:<" . $Q[$i] . ">"; break; ) ) if ($i == count ($Q))( mysqli_query($con, "COMMIT"); return 1; ) else ( mysqli_query($con, "ROLLBACK"); return 0; ) )

Koska tämä on ensimmäinen Googlen tulos "php mysql -tapahtumalle", ajattelin lisätä vastauksen, joka osoittaa selvästi, kuinka tämä tehdään mysqlillä (kuten alkuperäiset kirjoittajat halusivat). Tässä on yksinkertaistettu esimerkki tapahtumista PHP/mysqli:n kanssa:

// Oletetaan, että käyttäjä haluaa luoda uuden "ryhmän". Teemme niin // samalla kun luomme "jäsenyyden" ryhmälle, joka // koostuu yksinomaan käyttäjästä itsestään (alku) Vastaavasti ryhmä // ja jäsentietueet tulisi luoda yhdessä tai ei ollenkaan // tämä kuulostaa työltä: $group_name = "Ttorstai Thumpers" $member_name = " EleventyOne"; mysqli($db_host,$db_user,$db_passwd,$db_name); // tarkista tämä // huomautus: tämä on tarkoitettu InnoDB-taulukoille, ei toimi MyISAM-taulukoiden kanssa. try ( $conn->autocommit(FALSE); // eli aloita tapahtuma // oletetaan, että TABLE-ryhmissä on auto_increment id -kenttä $query = "INSERT INTO ryhmiin (nimi) "; $query .= "ARVOT ("$ ryhmän_nimi""; $tulos = $conn - >insert_id; // viimeinen auto_inc id from *this* connection $query = "INSERT INTO group_membership (group_id,name) "; $query .= "ARVOT ("$ryhmän_tunnus","$jäsenen_nimi"); query($query); if (!$result) ( $result->free(); heittää uusi poikkeus ei-tapahtumatila $conn->autocommit(TRUE) // eli lopeta tapahtuma ) catch (poikkeus $e) ( // ennen tapahtuman peruuttamista, haluat //); varmistaaksesi, että poikkeus oli db-sidonnainen $conn->autocommit(TRUE);

Huomaa myös, että PHP 5.5:ssä on uusi mysqli::begin_transaction-menetelmä. PHP-tiimi ei kuitenkaan ole vielä dokumentoinut tätä, ja olen edelleen jumissa PHP 5.3:ssa, joten en voi kommentoida sitä.

Toinen menettelytapaesimerkki mysqli_multi_query:n kanssa olettaa, että $query täytetään puolipisteillä erotetuilla lauseilla.

Mysqli_begin_transaction($link); for (mysqli_multi_query ($linkki, $kysely); mysqli_more_results ($linkki); mysqli_next_result ($linkki)); ! mysqli_errno ($linkki)? mysqli_commit ($linkki) : mysqli_rollback ($linkki);

Luulen, että tajusin sen, eikö?

Mysql_query("ALOITA TAPAHTUMA"); $a1 = mysql_query("INSERT INTO rarara (l_id) VALUES("1")"); $a2 = mysql_query("INSERT INTO rarara (l_id) VALUES("2")"); if ($a1 ja $a2) ( mysql_query("COMMIT"); ) else ( mysql_query("ROLLBACK"); )

mysql_*-funktiot devalvoitiin (sekä php 5.5), koska parempia toimintoja ja koodirakenteita on kehitetty. Se, että ominaisuus on poistettu käytöstä, tarkoittaa, että sen parantamiseksi suorituskyvyn ja turvallisuuden näkökulmasta ei enää tehdä ponnisteluja. se tarkoittaa , että se on vähemmän luotettava tulevaisuutta ajatellen .

Jos tarvitset lisää syitä:

  • mysql_*-funktiot Valmisteltuja lausuntoja ei tueta.
  • mysql_*-funktiot eivät tue parametrien sidontaa.
  • mysql_*-funktiot eivät ole on toimintoja olio-ohjelmointiin.
  • lista jatkuu...

Tapahtuman aloittamiseksi sinun on suoritettava "beginTransaction()" -menetelmä "PDO"-luokan objektille. Katsotaanpa esimerkkiä php:ssä:

$dsn = "mysql:dbname=1;host=localhost"; $user = "juuri"; $salasana = ""; $ohjain = array(PDO::MYSQL_ATTR_INIT_COMMAND => "ASETA NIMET `utf8`"); try ( $db = new PDO($dsn, $käyttäjä, $salasana, $ohjain); //luo uusi PDO-luokkaobjekti vuorovaikutukseen tietokannan kanssa) catch (PDOException $e) ( echo "Yhteys epäonnistui: ". $ e ->getCode() ."|". $e->getMessage()); exit(); ) $db->beginTransaction(); //Aloita tapahtuma $db->exec("INSERT INTO user VALUES (1, "Kolya")"); $db->exec("INSERT INTO käyttäjän ARVOT (2, "Aleksei")"); $db->exec("INSERT INTO käyttäjän ARVOT (1, "Ivan")"); ... //seuraava commit() tai rollback()

Jos haluat tehdä muutoksia tapahtumaan, sinun on suoritettava commit()-menetelmä PDO-objektissa:

$db->commit();

Jos haluat peruuttaa muutokset (peruuttaa tapahtuman), sinun on kutsuttava rollBack()-metodi $db PDO-objektissa:

$db->palautus();

Huomaa, että jos aloitat tapahtuman etkä suorita sitä loppuun (eli et suorita commit()- tai rollback()-komentoa komentosarjassa), silloin kun komentosarja on valmis, tapahtuma peruutetaan automaattisesti, jos pysyvä yhteyttä tietokantaan ei ole muodostettu (PDO-määritettä ei ole asetettu: :ATTR_PERSISTENT => true). Sama tapahtuu, kun PDO-objekti ($db=null) tuhoutuu komentosarjakoodissa, tässä tapauksessa PDO katkaisee nykyisen yhteyden tietokantaan. PDO-ohjain peruuttaa tapahtuman, kun yhteys tietokantaan on valmis, tämä on erittäin kätevää, kun komentosarjat päättyvät epänormaalisti.

Tapahtumat ovat käytettävissä vain InnoDB-tyypin taulukoissa. Tapahtumat eivät ole käytettävissä MyISAM-taulukoissa.

MySQL:ssä on oletusarvoisesti automaattinen vahvistus käytössä. Tämä tarkoittaa jokaisen tietokantapyynnön vahvistamista (sitomista), mikä tarkoittaa, että jokainen tietokantapyyntö MySQL:ssä on oletusarvoisesti tapahtuma. Siksi tietojen lisääminen InnoDB-taulukoihin on hitaampaa kuin tietojen lisääminen MyISAM-taulukoihin. Kun tuot tietoja ja lisäät suuria tietomääriä InnoDB-taulukoihin, sinun tulee poistaa käytöstä automaattinen vahvistus ja muutosten vahvistaminen, eli ei commit jokaisen lisäyksen jälkeen, vaan useiden lisäysten jälkeen (sitou vasta, kun kyselyryhmä on suoritettu).

PDO-virheiden käsittely PHP:ssä ja tapahtumien peruuttaminen virheiden perusteella

Oletuksena PDO on asetettu äänettömään tilaan. Tämä tarkoittaa, että jos PDO:ssa tapahtuu virhe, poikkeusta ei tehdä ja komentosarja jatkuu. Virheitä ei havaita käyttämällä try catch -lohkoja. Virhekoodi ja kuvaus saadaan vain käyttämällä erityisiä menetelmiä PDO- tai PDOStatement-objektissa: errorCode() ja errorInfo(). Jotta SAN-virheet saadaan kiinni try..catchissa, sinun on vaihdettava virheenkäsittelytila PDO::ERRMODE_SILENT päällä PDO::ERRMODE_EXCEPTION. Huomio: tämän tilan asennuksen jälkeen on suositeltavaa käsitellä poikkeuksia jokaiselle tietokannan pyynnölle, koska jos tapahtuu virhe, komentosarja lakkaa toimimasta ja koko verkkosovellus pysähtyy. Jos asetat tämän tilan, muista käyttää try..catch-lohkoja jokaisessa pyynnössä virheiden havaitsemiseksi.

Katsotaanpa, kuinka sovelluslogiikka muuttuu virheenkäsittelytilan käyttöönoton jälkeen PDO::ERRMODE_EXCEPTION:

$dsn = "mysql:dbname=1;host=localhost";$user = "juuri";$salasana = ""; $ajuri = array(PDO::MYSQL_ATTR_INIT_COMMAND => "ASETA NIMET `utf8`"); try ( $db = new PDO($dsn, $user, $password, $driver); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //Aseta virheenkäsittelytila ​​ERRMODE_EXCEPTION ) catch (PDOException $ e) ( echo "Yhteys epäonnistui: ". $e->getCode() ."|". $e->getMessage(); exit(); ) try ( $db->beginTransaction(); //Aloita tapahtuma $db->exec("INSERT INTO user VALUES (1, "Kolya")"; $db->exec("INSERT INTO user VALUES (2, "Alexey")"); Ivan")"); catch (PDOException $e) ( //Kiinnitä virhe $db->rollBack(); echo "PDOException: ".$e->getCode() ."|". $e->getMessage( )); exit( ) $db->commit(); //Jos kaikki pyynnöt onnistuivat, sitoudu

Tämä on yksinkertainen esimerkki virheiden käsittelystä tapahtumia käytettäessä. Todellisissa sovelluksissa on välttämätöntä tarkastella virhekoodia. Jos kyseessä on esimerkiksi yhteyden katkeaminen MySQL-palvelimesta, komentosarjaa ei ole tarpeen lopettaa tapahtuman peruutuksen jälkeen. Tässä tapauksessa voit yrittää muodostaa uudelleen yhteyden SQL-palvelimeen jonkin ajan kuluttua ja yrittää suorittaa nykyisen kyselyn tai tapahtuman uudelleen. Jos kyseessä on esimerkiksi tietotyyppivirhe, ei tietenkään ole mitään järkeä toistaa pyyntöä, voit peruuttaa tapahtuman ja lopettaa komentosarjan. Jos tapahtuu tiettyjä virheitä, sinun ei tarvitse peruuttaa tapahtumaa ollenkaan. Yleensä sinun on katsottava virheen SQL-koodia - ja katsomisen jälkeen päätettävä, mitä tehdä seuraavaksi.

Katsotaanpa esimerkkiä:

Funktio connect_db() ( $dsn = "mysql:dbname=1;host=localhost"; $user = "root"; $salasana = ""; $driver = array(PDO:: MYSQL_ATTR_INIT_COMMAND => "SET NIMET `utf8` "); yritä ( $db = new PDO($dsn, $user, $password, $driver); //luo uusi PDO-luokan objekti vuorovaikutukseen tietokannan kanssa $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); //Aseta virheenkäsittelytila ​​ERRMODE_EXCEPTION ) catch (PDOException $e) ( echo "Yhteys epäonnistui: ". $e->getCode() ."|". $e->getMessage()); $db; ) function doQuery($db, $sql, $count_db = 0) ( if($count_db>5) ( echo "Yhteysyritysten määrä on ylittänyt sallitun rajan"; return false; ) try ( if($db-> inTransaction( )) ( echo "Tapahtuma on jo alkanut"; return false; ) $db->beginTransaction();//Aloita tapahtuma $db->exec($sql) (PDOException $e) ( if($db). -> inTransaction()) $db->rollBack(); if($e->errorInfo >= 2000&&$db=connect_db()) ( //jos virhekoodi on > 2000 (tämä on yhteyden katkeaminen tietokanta jne.) yritä sitten muodostaa yhteys uudelleen ja suorittaa kysely uudelleen return doQuery($db, $sql, $count_db++) else ( echo "PDOException: ".$e->getCode()."|). $e- >getMessage( return false ) ) if($db->inTransaction()) return $db->commit();

Huomaa menetelmä:

$db->inTransaction();

Se tarkistaa, onko tapahtuma alkanut vai ei. Tämä on erittäin tärkeää, koska jos kutsut beginTransaction()-menetelmää, kun tapahtuma on jo alkanut, tai päinvastoin kutsut rollBack()- tai commit()-metodia, kun tapahtuma ei ole vielä alkanut, saat kaikissa näissä tapauksissa FATAL virhe. Kyllä, joten tarkista aina, onko tapahtuma alkanut ennen kuin suoritat sen loppuun, muuten huomaat vain virheen ja sovelluksesi kaatuu.

Työtehtävistäni johtuen joudun joskus haastattelemaan "[senior|junior] python/django-kehittäjä", "tiimin johtaja". Suureksi yllätyksekseni huomasin, että yhdeksällä kymmenestä hakijasta, joiden ansioluettelossa on sanat ”Mysql/Innodb/transactions/triggers/stored proc etc”, ei ole mitään sanottavaa aiemmasta työkokemuksestaan ​​heidän kanssaan. Valitettavasti en koskaan saanut ainuttakaan kuvausta käyttötapauksesta.

Myöhemmin haastattelussa ehdotin, että yrität ehdottaa ratkaisua seuraavaan tilanteeseen:

Oletetaan, että olemme verkkopalvelu, joka puolestaan ​​käyttää jotain ulkopuolista maksullista API:ta (palvelun aktivointi, maksullinen sisältö tai mitä vain sydämesi kaipaa), eli palvelumme itse maksaa API:n käytöstä. Käyttäjä järjestelmässämme luo palvelun aktivointipyynnön, täyttää kaikki kentät ja napsauttaa viimeisellä sivulla "Aktivoi palvelu" -painiketta. Eli HTTP-pyynnön lähetyshetkellä meillä on tietue tietokannassamme (pyyntö palvelun aktivoimiseksi). Mikä on algoritmimme - kysyn ja jatkan:

Haemme käyttäjän saldon tietokannasta;
- jos tasapainoa on tarpeeksi, vedämme API:n;
- jos kaikki on kunnossa, poistamme palvelun summan saldosta, teemme PÄIVITYKSEN, sitoudumme, muuten peruutamme;
- vastaamme käyttäjälle.

Näyttää siltä, ​​​​että kaikki on triviaalia, mutta kun esitän ensimmäisen ja ilmeisimmän ongelman 10 kilpailevan pyynnön muodossa (että alussa he saavat kaikki saman saldon ja alkavat soittaa API:lle), he alkavat tarjota kehittyneintä ratkaisuja, alkaen 5 valinnan suorittamisesta (täytyy myöntää, että en ymmärtänyt tästä vaihtoehdosta mitään), automaattisesti kasvavien laskurien käyttö, ulkoiset välimuistit, uudet taulukot tietokannassa, lipsahdus ja kuka tietää mitä.

Kuten tiedät (ja kaikki ehdokkaat tiesivät tämän!), mysql:n innodb tarjoaa transaktiomekanismin ja mahdollisuuden lukita rivi riviltä. Tämän saman rivikohtaisen lukituksen käyttämiseksi riittää, kun lisäät FOR UPDATE -lausekkeen SELECT:n loppuun, esimerkiksi näin:

SELECT * FROM pyytää WHERE id=5 PÄIVITYSTÄ

Tapahtuma alkaa, eivätkä kaikki muut tietokannan istunnot pysty suorittamaan samanlaista pyyntöä ennen kuin tapahtumamme on suoritettu, ne vain odottavat. Tietue on luettavissa tilassa, joka riippuu tapahtuman eristystasosta.

On myös syytä huomata, että on parempi käyttää FOR UPDATE, kun automaattinen toimitus ei ole käytössä, koska riippumatta siitä, mitä olet lukinnut, lukko poistetaan ensimmäisen päivityksen jälkeen.

Se näyttää pieneltä asialta, näyttää itsestään selvältä, mutta 9/10...

Upd
edellinen nimi "Tapahtumat mysqlissa", jota ei kerrota artikkelissa, korvattiin nimellä "Tapahtumat mysqlissa ja VALITSE PÄIVITYSTÄ"

PS
Artikkelissa ei kerrota, että API on vedettävä tapahtuman sisällä ja mitä tehdä epäonnistuessa ja miten toimia poikkeuksellisissa tilanteissa.

Tapahtuma on mekanismi, jonka avulla useat tietokantaan tehdyt muutokset voidaan tulkita yhdeksi toiminnoksi. Joko kaikki muutokset hyväksytään tai ne kaikki hylätään. Mikään muu istunto ei voi käyttää taulukkoa, kun käynnissä on tapahtuma, joka tekee muutoksia kyseiseen taulukkoon. Jos yrität valita tietoja istunnostasi heti sen muuttamisen jälkeen, kaikki tehdyt muutokset ovat käytettävissä.

Tapahtumatietokantamoottori, kuten InnoDB tai BDB, aloittaa tapahtuman aloitustapahtumakomennolla. Tapahtuma on valmis, kun muutokset on tehty tai peruutettu. Voit suorittaa tapahtuman kahdella komennolla. Commit-komento tallentaa kaikki muutokset tietokantaan. Palautuskomento hylkää kaikki muutokset.

Alla oleva esimerkki luo tapahtumataulukon, lisää siihen tiedot, käynnistää sitten tapahtuman, joka poistaa tiedot ja peruuttaa lopuksi tapahtuman (poiston kumoaminen):

LUO TAULUKKO sample_innodb (id int(11) EI NULL auto_lisäys, nimi varchar(150) oletus NULL, ENSISIJAINEN AVAIN (id)) ENGINE=InnoDB OLETUSKORTTI=utf8; INSERT INTO sample_innodb ARVOT (1, "Aleksanteri"), (2, "Dmitry"); aloittaa liiketoimi; DELETE FROM sample_innodb WHERE id = 1; DELETE FROM sample_innodb WHERE id = 2; palautus;

Koska tapahtuma peruutettiin, taulukon tietoja ei poistettu.

Ja jos kirjoitimme palautuksen sijaan commit, molemmat rivit poistettaisiin.

Tapahtumat ovat pakollisia, kun haluat, että useita kyselyitä sovelletaan ja suoritetaan "samaan aikaan" tai mitään niistä ei suoriteta, jos jokin menee pieleen.

Esimerkki on maksujärjestelmä verkkosivustolla. Tilaus tulee ostohetkellä merkitä maksetuksi ja samalla veloittaa rahat käyttäjän saldosta. Jos jokin asia epäonnistuu, joko käyttäjä on ilman ostettua tuotetta ja rahaa tai kauppa on ilman tuotetta ja rahaa. Ja transaktioiden avulla voimme helposti välttää tämän.