
Many operational signals can be expressed as a simple database question: “Is this number going up faster than expected?” If you can write a MySQL query that returns a count, sum, or metric, you can build an alert when the MySQL query result increases. The challenge is less about writing the query and more about handling state (what was the last value?), choosing the right monitoring method (polling vs. triggers vs. CDC), and avoiding false positives from retries, backfills, and clock skew.
This guide walks through practical, production-friendly patterns to detect increases in query results, with examples you can adapt to counts, sums, and “new rows since last check” metrics.
What “query result increases” really means
There are three common interpretations. Decide which one you need, because the implementation differs:
- Absolute increase: alert if a metric is higher than the last time you checked (e.g., total users increased).
- Increase rate: alert if it increased by more than a threshold within a time window (e.g., errors in the last 5 minutes jumped).
- Crossing a boundary: alert if it crosses a fixed threshold (e.g., pending jobs > 10,000).
In practice, you often combine them: alert if the last-5-minutes count increased and exceeded a minimum threshold to reduce noise.
Step 1: Write a stable “signal” query
Your alert is only as good as the query. Prefer queries that are:
- Deterministic (same inputs → same result)
- Indexed (won’t become an accidental load test)
- Time-bounded (avoids scanning all history)
Example: count new orders in the last 5 minutes
SELECT COUNT(*) AS new_orders
FROM orders
WHERE created_at >= NOW() - INTERVAL 5 MINUTE;
Index tip: ensure orders(created_at) is indexed (or a composite index that starts with created_at) so this stays fast as the table grows.
Example: sum of failures in a window
SELECT COALESCE(SUM(failures), 0) AS failures_5m
FROM job_runs
WHERE started_at >= NOW() - INTERVAL 5 MINUTE;
Step 2: Decide how you’ll detect the increase
To alert when a MySQL query result increases, you must compare the current value to something: a previous value, a rolling baseline, or a threshold. There are three broad approaches.
| Approach | How it works | Pros | Cons |
|---|---|---|---|
| Polling + stored last value | Run query every N minutes; compare to last run; alert; update last value. | Simple, portable, works with read-only queries for the signal. | You must store state somewhere; can miss very short spikes between intervals. |
| Triggers + audit table | Use MySQL triggers to increment counters or log events; alert off the log. | Near-real-time, precise; no polling required for the core signal. | Requires write access and schema changes; triggers can add overhead. |
| CDC/binlog | Stream row changes from the MySQL binlog and compute metrics externally. | Scales well; rich event context; minimal query load. | More moving parts; needs careful security and operations. |
Pattern A: Polling with a “last value” state
This is the most common approach because it’s straightforward: run the query on a schedule and compare it to the last observed value. The main design decision is where to store that last value.
Option A1: Store state in MySQL (simple, but needs write privileges)
Create a small table for alert state:
CREATE TABLE alert_state (
name VARCHAR(100) PRIMARY KEY,
last_value BIGINT NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP
);
Then your checker does:
- Read
last_valuefor the alert name - Run the signal query to get
current_value - If
current_value > last_value(and optionally above a threshold), send an alert - Update
last_valuetocurrent_value
Important: this works best for monotonic metrics (e.g., total rows). For windowed metrics (like “last 5 minutes”), the value can go up and down naturally. In that case, compare to a baseline (moving average) or alert only on large jumps.
Option A2: Store state outside MySQL (best for read-only database access)
If you want the monitoring user to be read-only, store state in a safer place: a small key/value store, an internal monitoring DB, or even a file in a controlled runtime. This keeps production MySQL permissions tight while still enabling increase detection.
Minimal example: script that alerts on increase
Below is a compact example in Python-like pseudocode (adapt to your stack). It tracks the last value in a local file for clarity:
import json
import time
ALERT_NAME = "new_orders_5m"
THRESHOLD = 50 # alert only if it rises above 50
def load_state():
try:
return json.load(open("state.json"))
except FileNotFoundError:
return {}
def save_state(state):
json.dump(state, open("state.json", "w"))
def query_mysql_count(conn):
sql = """
SELECT COUNT(*)
FROM orders
WHERE created_at >= NOW() - INTERVAL 5 MINUTE;
"""
return conn.scalar(sql)
state = load_state()
last = state.get(ALERT_NAME, 0)
current = query_mysql_count(conn)
if current > last and current >= THRESHOLD:
send_alert(f"{ALERT_NAME} increased: {last} → {current}")
state[ALERT_NAME] = current
save_state(state)
Noise reducer: require both increase and minimum threshold. Otherwise, tiny fluctuations can trigger alerts constantly.
Pattern B: Use triggers when you need precision
If you need immediate detection (or you can’t tolerate missing short spikes), triggers can log changes as they happen. A common pattern is to create an append-only event table that records what you care about and then alert from that table.
Example: log inserts to an audit table
CREATE TABLE order_events (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
order_id BIGINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
DELIMITER //
CREATE TRIGGER orders_after_insert
AFTER INSERT ON orders
FOR EACH ROW
BEGIN
INSERT INTO order_events(order_id) VALUES (NEW.id);
END//
DELIMITER ;
Now your alert query becomes cheap and explicit:
SELECT COUNT(*) AS orders_last_5m
FROM order_events
WHERE created_at >= NOW() - INTERVAL 5 MINUTE;
Trade-off: triggers add overhead to writes. For high-throughput systems, carefully test the impact and consider partitioning/retention (e.g., keep only 7–30 days of events).
Pattern C: CDC/binlog for scalable, low-query monitoring
Change Data Capture (CDC) reads MySQL’s binlog and streams row-level changes to a consumer (like Kafka, a worker, or a managed pipeline). You compute metrics from events and trigger alerts based on those computed metrics.
This is powerful when:
- You have many signals and don’t want many polling queries
- You want rich context (which rows changed, which customer, etc.)
- You already have streaming infrastructure
The downside is complexity: deployments, offsets, reprocessing, and ensuring you don’t leak sensitive data in streams.
How to choose intervals, windows, and thresholds
A reliable “alert when MySQL query result increases” setup usually depends more on tuning than on code.
1) Pick an interval that matches how fast you need to react
- 1–2 minutes for user-facing incidents (payments failing, queue growth)
- 5–15 minutes for operational drift (slow growth, missing ingestion)
- Hourly/daily for business metrics (reporting completeness)
2) Prefer rate-based alerts for spiky metrics
If the metric naturally swings, alert on changes relative to a baseline. For example, compare current window to the previous window:
-- Compare this 5-minute window to the previous 5-minute window
SELECT
SUM(CASE WHEN created_at >= NOW() - INTERVAL 5 MINUTE THEN 1 ELSE 0 END) AS current_5m,
SUM(CASE WHEN created_at >= NOW() - INTERVAL 10 MINUTE
AND created_at < NOW() - INTERVAL 5 MINUTE THEN 1 ELSE 0 END) AS prev_5m
FROM orders
WHERE created_at >= NOW() - INTERVAL 10 MINUTE;
Then alert if current_5m > prev_5m * 2 (or another factor that fits your domain).
3) Add a “minimum volume” guardrail
Doubling from 1 to 2 is rarely an incident. Common guardrails:
- Absolute floor: alert only if current ≥ 50
- Delta floor: alert only if current − previous ≥ 25
Common pitfalls (and how to avoid them)
- Backfills and replays: data pipelines sometimes insert historical records. Use event time carefully and consider filtering by
created_atvsupdated_at. - Clock skew: if app servers write timestamps, your “last 5 minutes” window can be wrong. Prefer DB-generated timestamps or consistent time sources.
- Expensive full scans: always check the execution plan (
EXPLAIN). Add indexes or rewrite queries to be sargable. - Alert storms: implement deduplication/cooldowns (e.g., don’t alert more than once per 15 minutes per signal).
- Permission creep: keep monitoring users read-only when possible; if you must store state in MySQL, isolate it in a separate schema with minimal grants.
Operational best practices for database-backed alerts
- Document the query: what it measures, expected ranges, and who owns it.
- Version control your alert logic: treat queries like code.
- Test with known scenarios: simulate a spike and confirm timing and deduping.
- Track alert performance: duration, rows examined, and failure rate of checks.
- Secure connections: use TLS and rotate credentials; never run monitoring with broad write privileges.
Quick checklist: building an “increase” alert you can trust
- Define what “increase” means (absolute, rate, or threshold crossing).
- Write an indexed, time-bounded query.
- Choose detection method: polling (simplest), triggers (precise), or CDC (scalable).
- Store state safely (ideally outside production DB if you want strict read-only access).
- Add guardrails: minimum volume, dedupe/cooldown, and clear ownership.
Closing thought
Once you can express the signal as SQL, the reliable part is designing the comparison and state handling so that a genuine increase triggers a meaningful notification—without waking you up for normal variance. If you prefer a mobile-first workflow for scheduled, read-only database checks and notifications, tools like Query Alert can help streamline that last-mile alert delivery.