---
title: "Set up self-managed MySQL | Database Observability documentation"
description: "Set up Database Observability for MySQL using Grafana Alloy and send telemetry to Grafana Cloud."
---

> For a curated documentation index, see [llms.txt](/llms.txt). For the complete documentation index, see [llms-full.txt](/llms-full.txt).

# Set up self-managed MySQL

Set up Database Observability with Grafana Cloud to collect telemetry from MySQL using Grafana Alloy. You configure your database and Alloy to forward telemetry to Grafana Cloud.

## What you’ll achieve

In this article, you:

- Configure MySQL for monitoring.
- Run Alloy with the public preview Database Observability components.
- Forward telemetry to Grafana Cloud.

## Before you begin

Review these requirements:

- Use MySQL version 8.0 or later.

> Note
> 
> Alloy should connect directly to the database host. Avoid connecting Alloy to the database through a load balancer or connection pooler as it would limit Alloy’s ability to collect accurate telemetry.

## Set up the MySQL database

Prepare MySQL for monitoring and query introspection.

### Enable `Performance Schema`

Enable `Performance Schema` if it’s not already enabled and verify the setting:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SHOW VARIABLES LIKE 'performance_schema';
```

Expected result: Value is `ON`.

### Create a monitoring user and grant required privileges

Create the `db-o11y` user and grant base privileges:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
CREATE USER 'db-o11y'@'%' IDENTIFIED BY '<DB_O11Y_PASSWORD>';
GRANT PROCESS, REPLICATION CLIENT ON *.* TO 'db-o11y'@'%';
GRANT SELECT ON performance_schema.* TO 'db-o11y'@'%';
```

Replace *&lt;DB\_O11Y\_PASSWORD&gt;* with a secure password for the `db-o11y` MySQL user.

### Disable tracking of monitoring user queries

Prevent tracking of queries executed by the monitoring user itself:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
UPDATE performance_schema.setup_actors SET ENABLED = 'NO', HISTORY = 'NO' WHERE USER = 'db-o11y';
```

### Grant object privileges for detailed data

Grant access to specific schemas when you want detailed information:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
GRANT SELECT, SHOW VIEW ON <SCHEMA_NAME>.* TO 'db-o11y'@'%';
```

Replace *&lt;SCHEMA\_NAME&gt;* with the name of the schema you want to monitor.

Alternatively, if you’re unsure which specific schemas need access, grant broader read access to all schemas:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
GRANT SELECT, SHOW VIEW ON *.* TO 'db-o11y'@'%';
```

### Verify user privileges

Verify that the user exists and has the expected privileges:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SHOW GRANTS FOR 'db-o11y'@'%';
```

Expected output:

![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```none
+------------------------------------------------------------------------------+
| Grants for db-o11y@%                                                         |
+------------------------------------------------------------------------------+
| GRANT PROCESS, REPLICATION CLIENT ON *.* TO `db-o11y`@`%`                    |
| GRANT SELECT, SHOW VIEW ON *.* TO `db-o11y`@`%`                              |
| GRANT SELECT ON `performance_schema`.* TO `db-o11y`@`%`                      |
| GRANT INSERT, UPDATE ON `performance_schema`.`setup_actors` TO `db-o11y`@`%` |
+------------------------------------------------------------------------------+
```

### Increase digest length limits

Increase `max_digest_length` and verify the setting:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SHOW VARIABLES LIKE 'max_digest_length';
```

Expected result: Value is `4096`.

Increase `performance_schema_max_digest_length` and verify the setting:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SHOW VARIABLES LIKE 'performance_schema_max_digest_length';
```

Expected result: Value is `4096`.

### Increase SQL text length limits (optional)

Increase `performance_schema_max_sql_text_length` to collect actual SQL text when you disable query redaction later. Verify the setting:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SHOW VARIABLES LIKE 'performance_schema_max_sql_text_length';
```

Expected result: Value is `4096`.

### Grant privileges to auto-enable consumers (optional)

Grant update privileges for the `performance_schema.setup_consumers` table if you want Alloy to auto-enable consumers:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
GRANT UPDATE ON performance_schema.setup_consumers TO 'db-o11y'@'%';
```

Then, enable the Alloy options `allow_update_performance_schema_settings` (refer to [docs](/docs/grafana-cloud/send-data/alloy/reference/components/database_observability/database_observability.mysql/#arguments)) and `query_samples.auto_enable_setup_consumers` (refer to [docs](/docs/grafana-cloud/send-data/alloy/reference/components/database_observability/database_observability.mysql/#query_samples)) as detailed in the documentation of the `database_observability.mysql` component.

Alternatively, enable consumers manually as described in the following sections.

### Enable CPU consumer for samples manually (optional)

Enable the CPU consumer when you want to collect CPU usage information together with query samples.

Check whether the `events_statements_cpu` consumer is enabled:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SELECT * FROM performance_schema.setup_consumers WHERE NAME = 'events_statements_cpu';
```

Enable the consumer if it’s disabled:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME = 'events_statements_cpu';
```

The `events_statements_cpu` consumer disables when the database restarts. Recommended: Let Alloy auto-enable `Performance Schema` consumers. To do so, follow the instructions in the previous section “Grant privileges to auto-enable consumers”.

### Enable wait event consumers manually (optional)

Enable wait event consumers when you want to collect wait events together with query samples.

Check whether the `events_waits_current` and `events_waits_history` consumers are enabled:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
SELECT * FROM performance_schema.setup_consumers WHERE NAME IN ('events_waits_current', 'events_waits_history');
```

Enable the consumers if they’re disabled:

SQL ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```sql
UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('events_waits_current', 'events_waits_history');
```

These consumers disable when the database restarts. Recommended: Let Alloy auto-enable `Performance Schema` consumers on your behalf. To do so, follow the instructions in the previous section “Grant privileges to auto-enable consumers”.

## Run and configure Alloy

Run Alloy and add the Database Observability configuration.

### Run the latest Alloy version

Alloy `1.16.0` or later is required for Database Observability. Find the latest stable version on [Docker Hub](https://hub.docker.com/r/grafana/alloy/tags). To update, refer to the [Alloy release notes](https://github.com/grafana/alloy/releases).

### Add the MySQL configuration blocks

Add these blocks to Alloy. Replace *&lt;DB\_NAME&gt;*. Create a `local.file` with the Data Source Name string, for example, `<DB_USER>:<DB_PASSWORD>@tcp(<DB_HOST>:<DB_PORT>)/`:

Alloy ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```alloy
local.file "mysql_secret_<DB_NAME>" {
  filename  = "/var/lib/alloy/mysql_secret_<DB_NAME>"
  is_secret = true
}

prometheus.exporter.mysql "mysql_<DB_NAME>" {
  data_source_name  = local.file.mysql_secret_<DB_NAME>.content
  enable_collectors = ["perf_schema.eventsstatements"]
  perf_schema.eventsstatements {
    limit      = 100
    text_limit = 0
  }
}

database_observability.mysql "mysql_<DB_NAME>" {
  data_source_name  = local.file.mysql_secret_<DB_NAME>.content
  forward_to        = [loki.relabel.database_observability_mysql_<DB_NAME>.receiver]
  targets           = prometheus.exporter.mysql.mysql_<DB_NAME>.targets

  // OPTIONAL: provide additional information specific to the cloud provider
  // that hosts the database to enable certain infrastructure observability
  // features. See documentation of `database_observability.mysql` for
  // other cloud providers.
  cloud_provider {
    aws {
      arn = "<AWS_RDS_DB_ARN>"
    }
  }
}

loki.relabel "database_observability_mysql_<DB_NAME>" {
  forward_to = [loki.write.logs_service.receiver]

  // OPTIONAL: add any additional relabeling rules
  // (must be consistent with rules in "discovery.relabel")
  rule {
    target_label = "instance"
    replacement  = "<INSTANCE_LABEL>"
  }
  rule {
    target_label = "<CUSTOM_LABEL_1>"
    replacement  = "<CUSTOM_VALUE_1>"
  }
}

discovery.relabel "database_observability_mysql_<DB_NAME>" {
  targets = database_observability.mysql.mysql_<DB_NAME>.targets

  rule {
    target_label = "job"
    replacement  = "integrations/db-o11y"
  }

  // OPTIONAL: add any additional relabeling rules
  // (must be consistent with rules in "loki.relabel")
  // OPTIONAL: relabel `instance` to `dsn` before overwriting `instance`;
  // the `dsn` label is used in the integration with the knowledge graph
  rule {
    source_labels = ["instance"]
    target_label  = "dsn"
  }
  rule {
    target_label = "instance"
    replacement  = "<INSTANCE_LABEL>"
  }
  rule {
    target_label = "<CUSTOM_LABEL_1>"
    replacement  = "<CUSTOM_VALUE_1>"
  }
}

prometheus.scrape "database_observability_mysql_<DB_NAME>" {
  targets    = discovery.relabel.database_observability_mysql_<DB_NAME>.output
  forward_to = [prometheus.remote_write.metrics_service.receiver]
}
```

Replace the placeholders:

- `DB_NAME`: Database name Alloy uses in component identifiers (appears in component names and secret filenames).
- `AWS_RDS_DB_ARN`: Amazon RDS database ARN for cloud provider integration (optional).
- `INSTANCE_LABEL`: Value that sets the `instance` label on logs and metrics (optional).
- `CUSTOM_LABEL_1`, `CUSTOM_VALUE_1`: Optional custom label key and value you attach to logs and metrics.
- Secret file content DSN example: `DB_USER:DB_PASSWORD@tcp(DB_HOST:DB_PORT)/`.
  
  - `DB_USER`: Database user Alloy uses to connect (for example, `db-o11y`).
  - `DB_PASSWORD`: Password for the database user.
  - `DB_HOST`: Hostname or IP address of the database.
  - `DB_PORT`: Database port number.

Find more about the options supported by the `database_observability.mysql` component in the [reference](/docs/grafana-cloud/send-data/alloy/reference/components/database_observability/database_observability.mysql/) documentation.

### Add Prometheus and Loki write configuration

Add the Prometheus remote write and Loki write configuration. From Grafana Cloud, open your stack to get the URLs and generate API tokens:

Alloy ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```alloy
prometheus.remote_write "metrics_service" {
  endpoint {
    url = sys.env("GCLOUD_HOSTED_METRICS_URL")

    basic_auth {
      password = sys.env("GCLOUD_RW_API_KEY")
      username = sys.env("GCLOUD_HOSTED_METRICS_ID")
    }
  }
}

loki.write "logs_service" {
  endpoint {
    url = sys.env("GCLOUD_HOSTED_LOGS_URL")

    basic_auth {
      password = sys.env("GCLOUD_RW_API_KEY")
      username = sys.env("GCLOUD_HOSTED_LOGS_ID")
    }
  }
}
```

Replace the placeholders:

- `GCLOUD_HOSTED_METRICS_URL`: Your Grafana Cloud Prometheus remote write URL.
- `GCLOUD_HOSTED_METRICS_ID`: Your Grafana Cloud Prometheus instance ID (username).
- `GCLOUD_HOSTED_LOGS_URL`: Your Grafana Cloud Loki write URL.
- `GCLOUD_HOSTED_LOGS_ID`: Your Grafana Cloud Loki instance ID (username).
- `GCLOUD_RW_API_KEY`: Grafana Cloud API token with write permissions.

## Run and configure Alloy with the Grafana Kubernetes Monitoring Helm chart

Extend your `values.yaml` when you use the k8s-monitoring Helm chart and set `databaseObservability.enabled` to true within the MySQL integration.

YAML ![Copy code to clipboard](/media/images/icons/icon-copy-small-2.svg) Copy

```yaml
integrations:
  collector: alloy-singleton
  mysql:
    instances:
      - name: <DB_NAME>
        jobLabel: integrations/db-o11y
        exporter:
          enabled: true
          collectors:
            perfSchemaEventsStatements:
              enabled: true
          dataSource:
            host: <DB_HOST>
            auth:
              usernameKey: <DB_USERNAME_SECRET_KEY>
              passwordKey: <DB_PASSWORD_SECRET_KEY>
        databaseObservability:
          enabled: true
        secret:
          create: false
          name: <DB_NAME>
          namespace: mysql
        logs:
          enabled: true
          labelSelectors:
            app.kubernetes.io/instance: <DB_NAME>
```

Replace the placeholders:

- `DB_NAME`: Database name Alloy uses in component identifiers (appears in component names and secrets).
- `DB_HOST`: Hostname or IP address of the database.
- `DB_USERNAME_SECRET_KEY`: Kubernetes secret key containing database user.
- `DB_PASSWORD_SECRET_KEY`: Kubernetes secret key containing database password.

To see the full set of values, check out the k8s-monitoring Helm chart [docs](https://github.com/grafana/k8s-monitoring-helm/blob/main/charts/k8s-monitoring/charts/feature-integrations/docs/integrations/mysql.md#database-observability) or the [example configuration](https://github.com/grafana/k8s-monitoring-helm/tree/main/charts/k8s-monitoring/docs/examples/features/database-observability/mysql)

## Next steps

For an overview of key concepts, refer to [Introduction to Database Observability](/docs/grafana-cloud/monitor-applications/database-observability/introduction/).
