Inleiding tot MySQL-triggers

De kans is groot: u weet wat een database-trigger is, althans in conceptuele termen. De kans is nog groter dat je weet dat MySQL triggers ondersteunt en deze al geruime tijd ondersteunt. Ik vermoed, zelfs gewapend met deze kennis, dat velen van jullie geen misbruik maken van triggers met MySQL. Het zijn een van die dingen die absoluut in je ontwikkeltoolbox moeten staan, omdat ze de manier waarop je naar je gegevens kijkt echt kunnen veranderen.


Introductie: Wat is een trigger

"Naarmate toepassingen echter steeds ingewikkelder worden, kunnen we de lagen van een toepassing abstraheren om te verwerken wat ze zouden moeten doen, hoe groter onze bruikbaarheid voor interne ontwikkeling wordt."

Voor niet-ingewijden is een trigger een regel die u op een tabel zet die in feite zegt dat wanneer u iets in deze tabel VERWIJDEREN, UPDATEN of INVOEGEN, u ook iets anders doet. We willen bijvoorbeeld een wijziging registreren, maar in plaats van twee afzonderlijke query's te schrijven, een voor de wijziging en een voor het logboek, kunnen we in plaats daarvan een trigger schrijven met de tekst: "Wanneer deze rij is bijgewerkt, maakt u een nieuwe rij in een andere tabel om te vertellen dat de update is gemaakt ". Het voegt een beetje overhead toe aan de initiële zoekopdracht, maar aangezien er geen twee pakketten naar uw database reizen om twee afzonderlijke dingen te doen, is er een algehele prestatiewinst (in theorie toch).

Triggers werden geïntroduceerd in MySQL in versie 5.0.2. De syntaxis voor een trigger is een beetje vreemd bij de eerste blush. MySQL gebruikt de ANSI SQL: 2003-standaard voor procedures en andere functies. Als u vertrouwd bent met een programmeertaal in het algemeen, is het niet zo moeilijk te begrijpen. De specificatie is niet vrij beschikbaar, dus ik zal mijn best doen om eenvoudige structuren te gebruiken en uit te leggen wat er binnen de trigger gebeurt. Je zult te maken hebben met dezelfde logische structuren die elke programmeertaal biedt.

Zoals ik hierboven al zei, zullen triggers procedureel worden uitgevoerd op UPDATE, DELETE en INSERT-evenementen. Wat ik niet noemde, is dat ze kunnen worden uitgevoerd vóór of na het gedefinieerde evenement. Daarom zou je een trigger kunnen hebben die vuurt voor een DELETE of na een DELETE, enzovoort enzovoort. Dit betekent dat je een trigger kunt hebben die vuurt voor een INSERT en een afzonderlijke trigger die na een INSERT wordt afgevuurd, die erg krachtig kan zijn.

Ik ga drie toepassingen bekijken die je zou kunnen overwegen toe te voegen aan je gereedschapskist. Er zijn verschillende manieren waarop ik me niet ga bezighouden, omdat ik denk dat er betere methoden zijn om dezelfde resultaten te krijgen, of dat ze hun eigen zelfstudie verdienen. Elk van deze toepassingen die ik aan het verkennen ben, heeft een tegenhanger in uw logische serverlaag en zijn geen nieuwe concepten. Naarmate toepassingen echter steeds ingewikkelder worden, kunnen we de lagen van een toepassing abstraheren om te verwerken wat ze zouden moeten doen, hoe groter onze bruikbaarheid voor interne ontwikkeling wordt..


Begin: mijn tafelstructuur, hulpmiddelen en notities

Ik werk met een mythisch wagensysteem, met items die prijzen hebben. Ik heb geprobeerd de gegevensstructuur zo eenvoudig mogelijk te houden, alleen ter illustratie. Ik noem kolommen en tabellen voor begrip, en niet voor productiegebruik. Ik gebruik ook TIMESTAMPS in plaats van andere alternatieven voor het gemak. Voor degenen die de thuisversie van de game van vandaag spelen, gebruik ik de tabelnamen van carts, cart_items, cart_log, items, items_cost.

Let op: gedurende deze tutorial zal ik zeer eenvoudige vragen gebruiken om mijn punten uit te drukken. Ik ben geen variabele aan het binden, omdat ik geen gebruikersinvoer gebruik. Ik wil de vragen zo gemakkelijk mogelijk te lezen maken, maar gebruik deze zelfstudie niet voor andere praktische triggerprogramma's. Ik weet dat hier misschien een paar opmerkingen over zijn, dus beschouw dit als mijn disclaimer.

Ik gebruik de PHP Quick Profiler met partikelboom om de uitvoeringstijden te bekijken. Ik gebruik ook de database-abstractielaag die in de tool wordt aangeboden, alleen voor mijn eigen voordeel. Het is een leuke tool en doet veel meer dan alleen SQL-uitvoeringstijden.

Ik gebruik ook Bieslook om de DB-effecten te illustreren en mijn triggers te maken. Bieslook is alleen MySQL 5+ en lijkt erg op PHPMyAdmin. Het is mooier, maar ook veel buggieriger op dit moment. Ik gebruik Bies, eenvoudig omdat het goede screenshots geeft over wat er met de vragen gebeurt.

Nog een snel berichtje. Mogelijk moet u het scheidingsteken voor MySQL wijzigen terwijl u een trigger maakt. Het natuurlijke scheidingsteken voor MySQL is; maar aangezien we dat scheidingsteken gebruiken voor onze toegevoegde query's, moet u het scheidingsteken expliciet hernoemen als u dit maakt via de opdrachtregel. Ik heb ervoor gekozen om dit niet te laten zien, omdat het gebruiken van Bies, het niet nodig is om het scheidingsteken te veranderen.

Als u een scheidingsteken wilt wijzigen, doet u dit gewoon vóór uw triggeropdracht:

 DELIMITER $$

En dit na uw trigger-opdracht:

 DELIMITER;

The Easy Trigger: Data Integrity

Als je zelfs maar de kleinste normalisatie van je databasestructuur uitvoert, kom je waarschijnlijk een tijd tegen dat je de hoofdgegevensbron hebt verwijderd, maar er nog steeds fragmenten rondlopen in je gegevensstroom. U kunt bijvoorbeeld een cart_id hebben waarnaar wordt verwezen in twee of drie tabellen zonder externe sleutels, vooral omdat externe sleutels niet worden ondersteund met de MyISAM-engine.

Wat je waarschijnlijk in het verleden hebt gedaan, is zoiets als dit (vereenvoudigd ter illustratie):

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

Afhankelijk van hoe goed u zichzelf organiseert, heeft u mogelijk één API of methode waarmee u uw winkelwagentjes kunt opschonen. Als dat het geval is, hebt u uw logica geïsoleerd om deze twee query's uit te voeren. Als dat niet het geval is, moet u altijd onthouden dat u uw winkelwagenitems moet wissen wanneer u een specifieke winkelwagen verwijdert. Niet moeilijk, maar als je het vergeet, verlies je je data-integriteit.

Voer onze trigger in. Ik ga een zeer eenvoudige trigger maken zodat wanneer ik een winkelwagentje verwijder, mijn trigger wordt geactiveerd om alle winkelwagenitems met dezelfde cart_id te verwijderen:

 CREATE TRIGGER 'tutorial'. 'Before_delete_carts' VOOR VERWIJDEREN 'trigger_carts' VOOR ELKE RIJ BEGIN DELETE FROM trigger_cart_items WHERE OLD.cart_id = cart_id; EINDE

Zeer eenvoudige syntaxis zoals ik hierboven al zei. Laten we door elke regel gaan.

Op mijn eerste regel staat 'CREATE TRIGGER' tutorial '.' Before_delete_carts '". Ik vertel MySQL om een ​​trigger in de database "tutorial" te maken om de naam "before_delete_carts" te krijgen. Ik noem mijn triggers meestal met de formule "When_How_Table". Dat werkt voor mij, maar er zijn tal van andere manieren om dit te doen.

Mijn tweede regel vertelt MySQL de definitie van deze trigger, "VOOR VERWIJDEREN" trigger_carts 'VOOR ELKE RIJ ". Ik vertel MySQL dat voordat je in deze tabel verwijdert, voor elke rij iets doet. Dat iets wordt vervolgens uitgelegd, binnen onze BEGIN en END. "DELETE FROM trigger_cart_items WHERE OLD.cart_id = cart_id;" Ik vertel MySQL voordat je delete_carts verwijdert, neem OLD.cart_id en verwijder ook uit trigger_cart_items. De OLD-syntaxis is de gedefinieerde variabele. We zullen dit bespreken in de volgende sectie waarin we OUD en NIEUW zullen combineren.

Er is echt niets om deze trigger te creëren. Het voordeel is dat uw logica voor gegevensintegriteit wordt verplaatst naar uw gegevenslaag, waarvan ik de oorzaak kan zijn, waar deze thuishoort. Er is ook een ander klein voordeel en dat is de lichte prestatiewinst, zoals hieronder te zien.

Twee vragen:

Eén zoekopdracht met een trigger:

Zoals u kunt zien, is er een lichte prestatiewinst te verwachten. Mijn database die ik gebruik is op localhost met mijn server, maar als ik een afzonderlijke DB-server gebruikte, zou mijn prestatiewinst een beetje groter zijn vanwege de round-triptijd tussen de twee servers. Mijn trigger-verwijdering heeft een iets hogere tijd om te verwijderen, maar er is slechts één vraag, dus de totale tijd neemt af. Vermenigvuldig dit met alle code die u gebruikt om uw gegevensintegriteit te behouden en de prestatiewinst wordt op zijn minst bescheiden.

Eén opmerking over de uitvoering, de eerste keer dat de trigger wordt uitgevoerd, is mogelijk een stuk langzamer dan de volgende keer. Ik gebruik triggers niet noodzakelijkerwijs voor de prestatiewinst, maar eerder om mijn datalogica naar mijn gegevenslaag te verplaatsen, net zoals je je presentatie van je opmaak naar je presentatielaag wilt verplaatsen, ook wel bekend als CSS.


The Pretty Easy Trigger: Logging en Auditing

Het volgende voorbeeld dat we zullen bekijken, gaat over logging. Stel dat ik elk item wil bijhouden dat in een winkelwagentje is geplaatst. Misschien wil ik de aankoopkoers van mijn winkelwagen controleren. Misschien wil ik gewoon een kopie van elk artikel in een winkelwagentje plaatsen, niet per se verkocht, alleen voor enig inzicht in de geest van mijn klanten. Misschien hebt u uw winkelwagenitems gemaakt als een MEMORY-tabel en wilt u alle items in een InnoDB-tabel registreren. Wat de reden ook is, laten we eens kijken naar een INSERT-trigger, die enkele goede mogelijkheden opent voor het loggen of controleren van onze gegevens.

Vóór triggers hebben we waarschijnlijk zoiets gedaan (opnieuw vereenvoudigd ter illustratie):

Nu kunnen we een heel eenvoudige trigger maken voor dit logproces:

 CREATE TRIGGER 'after_insert_cart_items' NA INVOEGEN 'trigger_cart_items' VOOR ELKE RIJ BEGIN INVOEGEN IN trigger_cart_log (cart_id, item_id) VALUES (NEW.cart_id, NEW.item_id); EINDE

Laten we dit opnieuw doornemen, gewoon zodat er duidelijkheid is over wat deze trigger doet. Eerst beginnen we met de regel "CREATE TRIGGER 'after_insert_cart_items'". Ik vertel MySQL opnieuw om een ​​trigger te maken met de naam "after_insert_cart_items". De naam kan "Foo" of "BullWinkle" zijn of hoe je het ook wilt noemen, maar nogmaals, ik geef er de voorkeur aan om mijn triggernamen te illustreren. Vervolgens zien we "NA INSERT AAN" trigger_cart_items 'VOOR ELKE RIJ'. Nogmaals, dit is te zeggen nadat we iets op trigger_cart_items hebben ingevoegd, voor elke rij die wordt ingegeven wat er tussen mijn BEGIN en END is.

Ten slotte hebben we gewoon: "INVOEGEN IN trigger_cart_log (cart_id, item_id) VALUES (NEW.cart_id, NEW.item_id);" Dit is een standaardquery met uitzondering van mijn twee waarden. Ik gebruik de NIEUWE waarde die is ingevoegd in de tabel cart_items.

En we hebben onze vragen in tweeën gesneden met de subtiele prestatiewinst:

En om te controleren of onze trigger werkt, zie ik de waarden in mijn tabel:

Dit is weer, relatief eenvoudig, maar we werken met een aantal waarden, die de complexiteit een beetje kunnen vergroten. Laten we iets moeilijkers bekijken.


The Harder Trigger: Business Logic

Op dit punt kunnen we de oude manier van meerdere query's overslaan met een enkele query. Ik stel me voor dat het een beetje saai zal worden om de prestaties van zoekopdrachten te blijven meten. Laten we in plaats daarvan een paar meer geavanceerde voorbeelden van triggers bespreken.

Bedrijfslogica is waar de bugs altijd omhoog kruipen. Ongeacht hoe voorzichtig of georganiseerd we zijn, glijdt er altijd iets door de kieren. Triggers op UPDATE verzachten dat slechts een beetje. We hebben enige macht in een trigger om te evalueren wat de OUDE waarde was, en hebben de NIEUWE waarde ingesteld op basis van de evaluatie. Stel dat we onze prijs van items altijd willen hebben als een opmaak van 30% van de kosten van de artikelen. Het is logisch dan dat, wanneer we onze kosten UPDATEN, we ook onze prijs moeten UPDATEN. Laten we dat doen met een trigger.

 CREATEER TRIGGER 'after_update_cost' NA UPDATE AAN 'trigger_items_cost' VOOR ELKE RIJ BEGIN UPDATE trigger_items SET-prijs = (NEW.cost * 1.3) WHERE item_id = NEW.item_id; EINDE

Wat we doen is de tabel met items bijwerken met een prijs op basis van de NEW.cost-tijden 1.3. Ik heb een prijs van $ 50 ingevoerd, dus mijn nieuwe prijs zou $ 65 moeten zijn.

Inderdaad, deze trigger werkte ook.

We moeten een beetje meer geavanceerd voorbeeld bekijken. We hebben al de regel om de prijs van een artikel te wijzigen op basis van de kosten. Laten we zeggen dat we onze kosten een beetje op peil willen brengen. Als de kosten minder dan $ 50 bedragen, zijn onze kosten eigenlijk $ 50. Als het meer dan $ 50 maar minder dan $ 100 kost, worden onze kosten $ 100 dollar. Hoewel mijn voorbeeld waarschijnlijk niet overeenkomt met een echte bedrijfsregel, passen we de kosten aan op basis van dagelijkse factoren. Ik probeer het voorbeeld eenvoudig te begrijpen.

Om dit te doen, gaan we weer werken met een UPDATE, maar deze keer zullen we het ontslaan voordat we onze vraag uitvoeren. We gaan ook werken met een IF-verklaring die voor ons beschikbaar is.

Dit is de nieuwe trigger:

 CREËER TRIGGER 'before_update_cost' VOOR UPDATE AAN 'trigger_items_cost' VOOR ELKE RIJ BEGINNEN ALS NEW.cost < 50 THEN SET NEW.cost = 50; ELSEIF NEW.cost > 50 EN NIEUW.kosten < 100 THEN SET NEW.cost = 100; END IF; END

Wat we nu doen, is niet om een ​​vraag te stellen, maar om de waarde gewoon te overschrijven. Ik zeg dat als de kosten minder dan $ 50 zijn, maak er dan gewoon $ 50 van. Als de kosten tussen $ 50 en $ 100 liggen, verdien dan $ 100. Als het daarboven staat, laat ik het gewoon hetzelfde blijven. Mijn syntaxis hier is niet zo vreemd van een andere taal aan de serverzijde. We moeten onze IF-clausule afsluiten met een END IF; maar anders dan dat, het is echt niet lastig.

Gewoon om te controleren of onze trigger werkt, heb ik een waarde van $ 30 voor de kosten ingevoerd, en het zou $ 50 moeten zijn:

Wanneer ik een prijs van $ 85 invoer, is hier de waarde:

En om te controleren of mijn NA UPDATE-trigger nog steeds werkt, moet mijn prijs nu $ 130 zijn:

Het leven is goed.


Conclusie

Ik heb alleen het topje van de ijsberg geraakt met triggers en MySQL. Hoewel er ontelbare toepassingen zijn voor triggers, ben ik in het verleden prima met elkaar overweg zonder ze door mijn gegevens in mijn logische laag te verwerken. Dat gezegd hebbende, de mogelijkheid om regels toe te voegen aan mijn gegevens in de gegevenslaag is gewoon logisch. Wanneer u de bescheiden prestatieverbeteringen toevoegt, is het voordeel nog groter.

We hebben nu te maken met gecompliceerde webtoepassingen met veel verkeer. Het gebruik van een trigger op een enkele pagina van de ijdelheidssite is misschien niet het beste gebruik van tijd en energie; een trigger op een complexe webtoepassing kan de wereld van verschil maken. Ik hoop dat je de voorbeelden leuk vond en laat me weten wat je nog meer moet uitleggen.