The “Same Server, Same Code… Different Bug?!” — A Sneaky MySQL Trigger Gotcha That Bit Our Live Site
Why a blank date crashed only on LIVE (and how baked‑in SQL_MODE inside triggers caused it)
Symptom: A harmless empty date turns fatal (but only on LIVE)
Front-end behavior: a user leaves a date blank and submits.
Expected: if no date, store “nothing” (NULL) and carry on.
Actual:
- TEST/local: request succeeds.
- LIVE: request fails with a fatal SQL error: "Incorrect datetime value: '0000-00-00 00:00:00' for column facture.Jour_j "
Same PHP. Same SQL. Same server. Same database export. Only LIVE fails.
The Rabbit Hole: It looked like a trigger or data issue… but both checked out
We checked the usual suspects:
- Triggers present? Same set on TEST and LIVE.
- Trigger bodies identical? Hashes matched (or differed in harmless ways).
- Zero-dates in source tables? None on LIVE; columns allowed NULL.
- Restoring LIVE into TEST? Everything worked fine there too.
Something invisible was different…
The Culprit: MySQL triggers bake in SQL_MODE at creation time
In MySQL, each trigger permanently stores the SQL_MODE that was active when it was created.
At runtime the trigger uses its stored mode, not the current session/global one.
Result:
- LIVE triggers were created under strict modes (e.g., NO_ZERO_DATE, NO_ZERO_IN_DATE, STRICT_TRANS_TABLES) → zero-dates are rejected.
- TEST triggers were created under a permissive mode (e.g., NO_AUTO_VALUE_ON_ZERO) → zero-dates are tolerated/coerced.
That’s why identical code/data behaved differently across DBs on the same server.
How to confirm quickly
Run this against both schemas to see each trigger’s baked-in SQL_MODE:
Why the front end made it worse (and how to harden it)
A blank form date often becomes an empty string ''. If your SQL uses Date_depart='$value', MySQL may coerce '' → '0000-00-00...' which strict modes reject.
Fix at the edge: send NULL for empty dates.
Two practical solutions
Option A — Make LIVE behave like TEST (re-bake trigger SQL_MODE)
Option B — Keep strict mode and normalize dates to NULL
- Edge (PHP): use NULL for blank dates (snippet above).
- In triggers: wrap any source date with NULLIF to collapse zero-dates to NULL before assignment: NULLIF(
, '0000-00-00') and/or NULLIF( , '0000-00-00 00:00:00').
“But I copied LIVE to TEST—why weren’t they identical?”
Because importing a dump into TEST recreates triggers under TEST’s current session SQL_MODE, not the mode they had on LIVE. You effectively “washed” the strictness during import, so the same data worked on TEST.
To make imports deterministic with the MySQL CLI:
Post-import audit:
Copy-paste checklist for your runbook
If a “simple” front-end action fails only on LIVE, check trigger SQL_MODE:
On the edge, send NULL for blank dates.
If keeping strict modes, NULLIF zero-dates inside triggers.
For parity with TEST, recreate triggers on LIVE under the desired mode.
On imports, pin SQL_MODE via --init-command and audit with information_schema.TRIGGERS.
Takeaway
From the UI, this looked trivial: submit a blank date. Under the hood it was a perfect storm of date coercion + trigger chains + baked-in SQL_MODE differences. The ultimate fix was straightforward, but finding it required looking past code/data diffs to the mode each trigger was born into. Next time LIVE behaves differently from TEST for no apparent reason, ask: “What SQL_MODE were these triggers created with?”
Stay Connected with Shoretec — Join the Shoretec Circle
Shoretec Solutions is a fast-growing custom software development company in Canada that specializes in building custom business applications for small and mid-sized organizations. We focus on creating efficient, logic-driven systems—such as ERP, CRM, project management, and internal operations tools—that streamline workflows, improve visibility, and scale with the business.
Get updates on our latest case studies, releases & tech insights. Follow Shoretec online.