# MySQL Audit Logging Guide

> The practical ways to audit MySQL activity, from the general query log to audit plugins, binlog CDC, and Bytebase, and where each one falls short.

Adela | 2026-06-03 | Source: https://www.bytebase.com/blog/mysql-audit-logging-guide/

---

Sooner or later someone asks who changed that row. Maybe it's an incident, maybe it's a SOC 2 or HIPAA audit, maybe a teammate just wants to know why a value looks wrong. MySQL gives you several ways to answer that question, and they vary a lot in detail, overhead, and how far you can trust them.

This guide walks through the practical options, from MySQL's built-in logging to audit plugins, binlog-based change capture, and Bytebase, so you can pick the setup that matches what you actually need. (For the PostgreSQL equivalent, see the [Postgres Audit Logging Guide](/blog/postgres-audit-logging/).)

## 1. Native MySQL Logging

MySQL ships with a [general query log](https://dev.mysql.com/doc/refman/8.0/en/query-log.html) that records every statement the server receives. It's the quickest way to get *some* visibility.

**Key settings**

Enable it in `my.cnf`:

```ini
[mysqld]
general_log = ON
general_log_file = /var/log/mysql/general.log
log_output = FILE          # or TABLE → mysql.general_log
```

Or toggle it at runtime without a restart:

```sql
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';   -- query it as mysql.general_log
```

This records every connection and statement, including who ran it and when.

**Pros**

- Built-in, no plugin needed.
- Captures everything, including `SELECT`s.

**Cons**

- Not a security tool: no filtering, no structured format, no tamper protection.
- Grows extremely fast and adds noticeable overhead; most teams cannot leave it on in production.
- Logs query text verbatim, including sensitive parameters.

The general log is fine for a short debugging window, but it was never meant to be a compliance audit trail. For anything long-lived, reach for an audit plugin (Section 4).

## 2. Trigger-Based Auditing

If what you want is **row-level changes**, the before and after values on an `UPDATE`, triggers will get you there.

**Example**

Create a table to store change history:

```sql
CREATE TABLE audit_log (
  id           BIGINT AUTO_INCREMENT PRIMARY KEY,
  table_name   VARCHAR(64),
  action       VARCHAR(10),
  changed_by   VARCHAR(128),
  changed_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  old_data     JSON,
  new_data     JSON
);
```

Then define a trigger. Unlike PostgreSQL, MySQL needs a **separate trigger per event** (you can't combine `INSERT OR UPDATE OR DELETE` in one definition):

```sql
CREATE TRIGGER users_audit_update
AFTER UPDATE ON users
FOR EACH ROW
  INSERT INTO audit_log (table_name, action, changed_by, old_data, new_data)
  VALUES ('users', 'UPDATE', CURRENT_USER(),
          JSON_OBJECT('id', OLD.id, 'email', OLD.email),
          JSON_OBJECT('id', NEW.id, 'email', NEW.email));
```

Repeat with `AFTER INSERT` (only `NEW`) and `AFTER DELETE` (only `OLD`) to cover all DML.

**Pros**

- Captures before/after data.
- Fully customizable schema.

**Cons**

- Must be defined per table *and* per event, which adds up quickly.
- Adds write overhead on busy tables.
- Captures the database user (`CURRENT_USER()`), not the real end user; can't see `SELECT`s or DDL.

## 3. Binlog-Based Auditing

MySQL's [binary log](https://dev.mysql.com/doc/refman/8.0/en/binary-log.html) records every data change. With `ROW` format and a full row image, it becomes a complete change-data-capture (CDC) stream without touching application code or adding triggers.

**Example setup**

Enable row-based binary logging in `my.cnf`:

```ini
[mysqld]
log_bin = mysql-bin
binlog_format = ROW
binlog_row_image = FULL
server_id = 1
```

Inspect changes directly:

```bash
mysqlbinlog --base64-output=DECODE-ROWS --verbose mysql-bin.000001
```

Or stream them to audit storage with [Debezium](https://debezium.io/) or [Maxwell](https://maxwells-daemon.io/), which decode the binlog into structured JSON and forward it to Kafka, Elasticsearch, or S3.

**Pros**

- Captures all data changes automatically, with no per-table setup.
- Minimal performance impact, since it reads from infrastructure you already run for replication.
- With `binlog_row_image = FULL`, you get before/after values.

**Cons**

- Does not capture `SELECT` queries, only modifications.
- No clean end-user attribution: the binlog records the change, not who logically requested it.
- Binlog retention increases storage if a consumer falls behind.

This is the approach to reach for when you need **near-real-time CDC** for auditing, analytics, or event-driven systems.

## 4. Audit Plugins

For structured, compliance-grade audit logs, use a dedicated audit plugin. Which one you pick depends mostly on your distribution:

- **MariaDB Audit Plugin (`server_audit`)** is open source and widely used. It also loads into MySQL and Percona, so it's the go-to for Community Edition.
- **MySQL Enterprise Audit** is part of MySQL Enterprise Edition (commercial). You get structured JSON output with rich filtering by user and event class.
- **Percona Audit Log Plugin** is free, bundled with Percona Server for MySQL, and comparable in capability.

**Example: MariaDB `server_audit` (Community Edition)**

```sql
INSTALL PLUGIN server_audit SONAME 'server_audit.so';
```

```ini
[mysqld]
plugin-load-add        = server_audit=server_audit.so
server_audit_logging   = ON
server_audit_events    = CONNECT,QUERY,TABLE
server_audit_file_path = /var/log/mysql/audit.log
```

**Example: MySQL Enterprise Audit (Enterprise Edition)**

```ini
[mysqld]
plugin-load-add  = audit_log.so
audit_log        = FORCE_PLUS_PERMANENT
audit_log_format = JSON
audit_log_file   = /var/log/mysql/audit.log
audit_log_policy = ALL          # or LOGINS, QUERIES
```

You'll get structured records of connections, queries, and table access: who, what, and when. For step-by-step enablement of each plugin, plus an **application-level (API/cursor-based) auditing** approach in your app code, see the reference: [How to Enable Auditing in MySQL](/reference/mysql/how-to/how-to-enable-auditing-mysql).

**What to log for compliance**

A defensible audit trail for SOC 2, HIPAA, or PCI DSS generally needs authentication events (connect/disconnect, success and failure), privileged and DDL activity, access to sensitive tables, and a tamper-resistant destination. Ship the plugin output to a centralized, append-only store (a SIEM) rather than leaving it on the database host where the people you're auditing can reach it.

**Pros**

- Structured, filterable, compliance-grade.
- Captures logins, DDL, and DML (and `SELECT`s, depending on policy).

**Cons**

- MySQL Enterprise Audit requires a commercial license.
- Still records the **database user**. In most apps that's a single shared account (e.g. `app_user`), so it can't attribute actions to a real person.
- Needs log rotation and a SIEM/analysis pipeline.

## 5. Bytebase

Bytebase is a Database DevSecOps platform that provides a [centralized audit trail](https://docs.bytebase.com/security/audit-log) across your MySQL environments. It records _who did what, when, and why_, linking SQL actions to their context (issues, approvals, and deployments) while keeping sensitive data secure.

**What Bytebase Audits**

- **Query access:** logs _who queried which data_ and _when_ across SQL Editor, Admin Query, and Data Export.
- **Schema and data changes:** tracks _who made which changes_, _when they were approved_, and _through which workflow or Git commit_.
- **Governance controls:** built-in SQL review rules, approval flow, and role-based access help stop unauthorized actions before they happen rather than just recording them after the fact.
- **Actual end users, not just database users:** this is the part the other approaches can't do. In most applications every query rides the same connection user (like `app_user`), which makes it impossible to trace an action back to an individual with engine-native auditing or audit plugins. Because people operate through Bytebase, every action is attributed to the real end user, not a generic database account.

**Why It Matters**

- **Complete visibility** across read and write operations.
- **Privacy-safe auditing** with no sensitive data exposure (column masking metadata travels with the record).
- **Compliance-ready** logs aligned with SOC 2, ISO 27001, and GDPR.

You can also call the [API](https://docs.bytebase.com/integrations/api/audit-log) to ship audit logs to a centralized sink. For the full record format, the gaps Bytebase closes versus engine-native logs, and per-column masking detail, see [How Bytebase Handles Audit Logging](/blog/bytebase-audit-logging/).

## Conclusion

MySQL gives you several layers of auditing, from the basic general log up to a full governance solution. None of them is the single right answer, and most teams end up combining a few.

**Comparison Table**

| Approach | Performance Impact | Captures SELECTs | Captures Actor | Row-Level Detail | Best For |
|----------|-------------------|------------------|----------------|------------------|----------|
| **General Query Log** | Medium-High | ✅ | ⚠️ DB user only | ❌ | Development, debugging, short windows |
| **Triggers** | Medium-High | ❌ | ⚠️ DB user only | ✅ (before/after) | Critical tables needing full change history |
| **Binlog (CDC)** | Low | ❌ | ❌ | ✅ | Real-time CDC, event-driven systems, analytics |
| **Audit Plugins** | Medium | ✅ (by policy) | ⚠️ DB user only | ❌ | Compliance requirements, structured logging |
| **Bytebase** | N/A (app-level) | ✅ | ✅ End user | ✅ | Centralized governance, approval workflows, team collaboration |

- Use the **general query log** for short-lived debugging (captures database user only).
- Use **triggers** for fine-grained row-level auditing on critical tables (captures database user only).
- Use **binlog-based CDC** for near-real-time change capture without application changes.
- Use an **audit plugin** (Enterprise, Percona, or `server_audit`) for structured, compliance-grade statement logging (captures database user only).
- Add **Bytebase** for centralized auditing that tracks the actual end user, not just the shared database account, linking every action to the person who performed it with full context of approvals and workflows.

The honest takeaway: engine-native tools tell you what happened to the data, and a workflow layer like Bytebase tells you who actually asked for it. Layer them and you get both the visibility and the control you need for secure, compliant, well-governed MySQL operations.

---

Related reading:

- [Database Audit Logging: Two Layers, One Trail](/blog/database-audit-logging/): the hub: infrastructure-vs-workflow split, framework-by-framework and engine-by-engine guides
- [Postgres Audit Logging Guide](/blog/postgres-audit-logging/): the PostgreSQL counterpart to this guide
- [How to Enable Auditing in MySQL](/reference/mysql/how-to/how-to-enable-auditing-mysql): the hands-on reference: general query log, `server_audit`, Enterprise Audit, and API-based auditing with exact config
- [How Bytebase Handles Audit Logging](/blog/bytebase-audit-logging/): the workflow-layer record format, gaps closed, masking metadata, export paths
- [SOC 2 Audit Log Requirements](/blog/soc2-audit-logging/): what auditors actually want, four-field framework, six categories of admin activity