Introduktion till MySQL-utlösare

Chansen är att du vet vad en databasutlösare är, åtminstone i begreppsmässiga termer. Chanserna är ännu större så att du vet att MySQL stöder utlösare och har stött dem ganska länge. Jag skulle gissa, även beväpnad med den här kunskapen, att många av er inte utnyttjar triggers med MySQL. De är en av de saker som absolut borde vara i din utvecklingsverktygslådan, eftersom de verkligen kan ändra sättet att se på dina data.


Inledning: Vad är en utlösare

"Eftersom applikationerna blir alltmer komplicerade, desto mer kan vi abstrahera lagren i en applikation för att hantera vad de borde, desto större blir användbarheten för vår interna utveckling."

För de oinitierade är en utlösare en regel som du lägger på ett bord som i grunden säger, när du slår, UPPDATERAR eller INSERT något i den här tabellen, gör du också något annat. Vi kanske till exempel vill logga in en ändring, men istället för att skriva två separata frågor, en för förändringen och en för loggen kan vi istället skriva en trigger som säger "När denna rad uppdateras, skapa en ny rad i ett annat bord för att berätta att uppdateringen gjordes ". Det lägger lite överhuvud till den ursprungliga frågan, men eftersom det inte finns två paket som reser till din databas för att göra två separata saker, finns det en övergripande prestationsförstärkning (i teorin i alla fall).

Utlösare introducerades i MySQL i version 5.0.2. Syntaxen för en utlösare är lite främmande vid första rodnad. MySQL använder ANSI SQL: 2003-standarden för procedurer och andra funktioner. Om du är bekväm med ett programmeringsspråk i allmänhet är det inte så svårt att förstå. Specifikationen är inte tillgänglig, så jag gör mitt bästa för att använda enkla strukturer och förklara vad som händer inom utlösaren. Du kommer att hantera samma logiska strukturer som något programmeringsspråk ger.

Som jag nämnde ovan kommer utlösare att utföras procedurellt vid UPDATE, DELETE och INSERT händelser. Vad jag inte nämnde är att de kan utföras antingen före eller efter händelsen definierad. Därför kan du få en utlösare som kommer att elda före en DELETE eller efter en DELETE, så vidare och så vidare. Det betyder att du kan ha en trigger som brinner före en INSERT och en separat som brinner AFTER en INSERT, som kan vara mycket kraftfull.

Jag ska titta på tre användningsområden som du kan överväga att lägga till i din verktygslåda. Det finns flera användningsområden som jag inte kommer att delva in, eftersom jag tycker att det finns bättre metoder för att få samma resultat, eller de förtjänar sin egen handledning. Var och en av dessa användningsområden som jag utforskar har en motsvarighet i ditt logiklager på serversidan, och det är inte nya begrepp. Eftersom applikationer växer mer och mer komplicerade, desto mer kan vi abstrahera lagren i en applikation för att hantera vad de borde, desto större blir användbarheten för vår interna utveckling.


Början: Min tabellstruktur, Verktyg och anteckningar

Jag jobbar med ett mytiskt vagnssystem med varor som har priser. Jag har försökt att hålla datastrukturen så enkelt som möjligt bara för illustration. Jag heter kolumner och tabeller för förståelse, och inte för produktionsanvändning. Jag använder även TIMESTAMPS i stället för andra alternativ för enkelhet. För dem som spelar hemma-versionen av dagens spel använder jag tabellens namn på vagnar, cart_items, cart_log, items, items_cost.

Observera under hela denna handledning kommer jag att använda mycket enkla frågor för att uttrycka mina poäng. Jag binder inte någon variabel, eftersom jag inte använder någon användarinput. Jag vill göra frågorna så lätta att läsa som möjligt, men använd inte denna handledning för något annat än praktiska utlösningsapplikationer. Jag vet att det kan finnas en kommentar eller två om detta, så överväga detta min ansvarsfriskrivning.

Jag använder partikelträdet PHP Quick Profiler för att se exekveringstider. Jag använder också databasabstraktionsskiktet som tillhandahålls i verktyget för min egen fördel. Det är ett bra verktyg, och gör mycket mer än bara att ge SQL-exekveringstider.

Jag använder också Chive för att illustrera DB-effekterna och skapa mina triggers. Chive är endast MySQL 5+, och liknar PHPMyAdmin. Det är snyggare, men också mycket buggare för tillfället. Jag använder Chive, helt enkelt för att det ger bra skärmdumpar om vad som händer med frågorna.

En annan snabb notering. Det kan hända att du måste ändra avgränsaren för MySQL medan du skapar en trigger. Den naturliga avgränsaren för MySQL är; men eftersom vi kommer att använda den avgränsaren för våra tillfrågade frågor kan du behöva uttryckligen byta namn på avgränsaren om du skapar dessa via kommandoraden. Jag har valt att inte visa detta, för att använda Chive är det inte nödvändigt att ändra avgränsaren.

För att ändra en avgränsare, skulle du helt enkelt göra det innan du triggar kommandot:

 DELIMITER $$

Och det här efter ditt utlösande kommando:

 DELIMITER;

Easy Trigger: Data Integritet

Om du gör även den minsta normaliseringen till din databasstruktur har du förmodligen löpt ut till en tidpunkt där du har raderat huvuddatakällan, men fortfarande har fragment som springer runt i din dataström. Du kan till exempel ha en cart_id som refereras i två eller tre tabeller utan främmande nycklar, särskilt eftersom utländska nycklar inte stöds av MyISAM-motorn.

Vad du förmodligen har gjort tidigare är något som detta (förenklat för illustration):

 $ sql = 'DELETE FROM no_trigger_cart_items WHERE cart_id = 1'; $ rs = $ this-> db-> fråga ($ sql); $ sql = 'DELETE FROM no_trigger_carts WHERE cart_id = 1'; $ rs = $ this-> db-> fråga ($ sql);

Nu, beroende på hur väl du organiserar dig själv, kan du ha ett enda API eller en metod som du skulle rensa dina vagnar. Om så är fallet har du isolerat din logik för att köra dessa två frågor. Om så inte är fallet måste du alltid komma ihåg att rensa dina varukorgsposter när du tar bort en viss kundvagn. Inte svårt, men när du glömmer, förlorar du din dataintegritet.

Ange vår trigger. Jag ska skapa en väldigt enkel utlösare så att när jag tar bort en vagn, kommer min utlösare att avfyras för att radera vagnsobjekt som har samma cart_id:

 SKAPA TRIGGER 'tutorial'. 'Before_delete_carts' FÖRE DELETE ON 'trigger_carts' FÖR VARJE RAD BEGIN DELETE FROM trigger_cart_items WHERE OLD.cart_id = cart_id; SLUTET

Mycket enkel syntax som jag sa ovan. Låt oss gå igenom varje rad.

Min första rad anger "CREATE TRIGGER" handledning "." Before_delete_carts "". Jag berättar för MySQL att skapa en trigger på databasen "tutorial" för att få ett namn på "before_delete_carts". Jag brukar namnge mina triggers med formeln "When_How_Table". Det fungerar för mig, men det finns många andra sätt att göra detta.

Min andra raden berättar MySQL för definitionen av denna utlösare, "FÖRE DELETE ON" trigger_carts "FÖR VARJE RAD". Jag säger till MySQL att innan du tar bort på den här tabellen, gör varje rad något för varje rad. Att något förklaras nästa, inom vår början och slut. "DELETE FROM trigger_cart_items WHERE OLD.cart_id = cart_id;" Jag berättar för MySQL innan du tar bort från trigger_carts, tar OLD.cart_id och tar även bort från trigger_cart_items. Den gamla syntaxen är den definierade variabeln. Vi kommer att diskutera detta i nästa avsnitt där vi kombinerar OLD och NEW.

Det finns verkligen inget att skapa denna utlösare. Fördelen är att flytta din dataintegritetslogik till ditt datalager, vilket jag kunde göra är fallet, var det hör hemma. Det finns också en annan liten fördel och det är den lilla prestationsförstärkningen, se nedan.

Två frågor:

En fråga med en utlösare:

Som du kan se finns det en liten prestationsförbättring, vilket borde förväntas. Min databas som jag använder är på localhost med min server, men om jag hade använt en separat DB-server skulle min prestationsförbättring vara lite större på grund av rutttid mellan de två servrarna. Min trigger-borttagning har en lite högre tid att radera, men det finns bara en fråga, så den totala tiden minskar. Multiplicera detta över hela koden som du använder för att hålla din dataintegritet, och prestationsförstärkningen blir åtminstone blygsam.

En anteckning om prestanda, första gången avtryckaren körs, kan det vara mycket långsammare än efterföljande tider. Jag använder inte utlösare nödvändigtvis för prestationsförstärkningen, utan snarare att flytta min datalogik till mitt datalager, precis som att du vill flytta din presentation från din markering till presentationslagret, annars kallat CSS.


Pretty Easy Trigger: Logging and Auditing

Nästa exempel som vi kommer att titta på kommer att hantera loggning. Säg att jag vill hålla reda på varje objekt som placeras i en vagn. Kanske vill jag övervaka min kundvagns köpinkomst. Kanske vill jag bara få en kopia av varje föremål placerad i en vagn, inte nödvändigtvis såld, bara för viss inblick i mina kunders sinne. Kanske skapade du dina varukorgsposter som ett MEMORY-bord, och du vill logga alla objekt i ett InnoDB-bord. Oavsett orsaken, låt oss titta på en INSERT-utlösare som öppnar några bra möjligheter att logga eller granska våra data.

Innan triggers gjorde vi förmodligen något som detta (igen förenklat för illustration):

Nu kan vi skapa en mycket enkel utlösare för denna loggningsprocess:

 SKAPA TRIGGER 'after_insert_cart_items' INTER INSERT ON 'trigger_cart_items' FÖR VARJE RAD BEGIN INSERT IN trigger_cart_log (cart_id, item_id) VALUES (NEW.cart_id, NEW.item_id); SLUTET

Låt oss springa igenom det här igen, bara så det finns tydlighet för vad den här utlösaren gör. Först börjar vi med raden "CREATE TRIGGER 'after_insert_cart_items'". Jag berättar igen MySQL för att skapa en trigger med namnet "after_insert_cart_items". Namnet kan vara "Foo" eller "BullWinkle" eller vad du vill kalla det, men igen föredrar jag att illustrera mina utlösarnamn. Därefter ser vi, "Efter INSERT ON" trigger_cart_items "FÖR VARJE RÅD". Återigen säger detta efter att vi har lagt in något på trigger_cart_items, för varje rad infogad kör vad som är mellan mina BEGIN och END.

Slutligen har vi bara "INSERT IN trigger_cart_log (cart_id, item_id) VALUES (NEW.cart_id, NEW.item_id);" vilket är en standardfråga med undantag för mina två värden. Jag använder det nya värdet som läggs in i cart_items tabellen.

Och vi har klippt våra frågor i hälften med den subtila prestationsförstärkningen:

Och bara för att kontrollera att vår trigger fungerar, ser jag värdena i mitt bord:

Det här är igen, relativt enkelt, men vi arbetar med ett par värderingar, vilket bara kan öka komplexiteten. Låt oss titta på något lite hårdare.


Den hårdare utlösaren: Business Logic

Vid denna tidpunkt kan vi hoppa över den gamla vägen för flera frågor med en enda fråga. Jag föreställer mig att det kommer att bli bara en väsande bit tråkig att fortsätta att mäta resultatet av frågor. Låt oss istället komma in på några fler exempel på triggers.

Affärslogik är där buggarna alltid kryper upp. Oavsett hur noggrann eller organiserad vi är, glider något alltid genom sprickorna. Utlösare på UPDATE mildra det bara lite. Vi har lite kraft i en trigger för att utvärdera vad det gamla värdet var och ange det nya värdet baserat på utvärderingen. Säg till exempel att vi alltid vill ha vårt pris på varor för att vara en 30% uppräkning av kostnaden för föremålen. Det är så naturligt att när vi UPPDATERAR vår kostnad måste vi UPPDATERA vårt pris. Låt oss hantera det med en utlösare.

 CREATE TRIGGER 'after_update_cost' Efter uppdatering på 'trigger_items_cost' FÖR VARJE RÅD BEGIN UPDATE trigger_items SET price = (NEW.cost * 1,3) VAR item_id = NEW.item_id; SLUTET

Vad vi gör är att uppdatera tabellerna med ett pris baserat på NEW.cost times 1.3. Jag angav en kostnad på $ 50, så mitt nya pris borde vara $ 65.

Visst nog har denna utlösare också fungerat.

Vi behöver ta en titt på ett lite mer avancerat exempel. Vi har redan regel att ändra priset på ett objekt baserat på det kostar. Låt oss säga att vi vill jämföra våra kostnader lite. Om kostnaden är mindre än $ 50 är vår kostnad faktiskt $ 50. Om det kostar över $ 50 men mindre än $ 100 blir vår kostnad $ 100 dollar. Medan mitt exempel antagligen inte stämmer överens med en sann affärsregel, anpassar vi kostnaden baserat på faktorer varje dag. Jag försöker bara att hålla exemplet lätt att förstå.

För att göra detta ska vi igen jobba med en UPDATE men den här gången kommer vi att skjuta den innan vi utför vår fråga. Vi kommer också att arbeta med ett IF-uttalande, vilket är tillgängligt för oss.

Här är den nya utlösaren:

 SKAPA TRIGGER "before_update_cost" FÖRE UPPDATERING PÅ "trigger_items_cost" FÖR VARJE RAD BEGINN OM NEW.cost < 50 THEN SET NEW.cost = 50; ELSEIF NEW.cost > 50 och new.cost < 100 THEN SET NEW.cost = 100; END IF; END

Vad vi gör nu ringer inte en fråga, utan bara överdriver värdet. Jag säger om kostnaden är mindre än $ 50, då gör du bara $ 50. Om kostnaden är mellan $ 50 och $ 100, gör det $ 100. Om det är ovanför så lät jag bara stanna detsamma. Min syntax här är inte det främmande från något annat sidorspråk på servern. Vi behöver stänga vår IF-klausul med ett slut IF; men annat än det är det verkligen inte knepigt.

Bara för att kontrollera om vår trigger fungerar, har jag lagt in ett värde på $ 30 för kostnaden, och det borde vara $ 50:

När jag anger en kostnad på $ 85, här är värdet:

Och bara för att kontrollera om min AFTER UPDATE-utlösare fortfarande fungerar, bör mitt pris nu vara $ 130:

Livet är gott.


Slutsats

Jag har bara rört toppen av isberget med utlösare och MySQL. Medan det finns otaliga användningsområden för triggers, har jag blivit ganska bra tidigare utan dem genom att hantera mina data i mitt logiklager. Med det sagt är möjligheten att lägga till regler till mina data i datalaget bara meningsfullt. När du lägger till i de blygsamma prestandaförbättringarna är fördelen ännu större.

Vi måste hantera komplicerade applikationer med hög trafikwebben nu. Medan du använder en trigger på en enda sida, kan fåfänga inte vara den bästa användningen av tid och energi. en utlösare på en komplex webbapplikation kan göra skillnadens värld. Jag hoppas att du haft exemplen, och snälla låt mig veta vad som behöver ytterligare förklaring.