database
Grounded corpus (Tier-1 consultation): symptom → index/strategy
decisions come grounded — ./scripts-run <skills-root>/corpus-grounding/scripts/ground search --manifest <skills-root>/database/data/manifest.json "<symptom>" returns root
cause, strategy, good-code sketch, anti-pattern, and the verification
probe (EXPLAIN expectation). Corpus:
data/query-tuning.csv (PostgreSQL 16 /
MySQL 8-derived).
When to use
Use when designing schemas, optimizing queries, adding indexes, or troubleshooting database performance.
Do NOT use when:
- Writing framework-specific ORM models (use the matching skill — e.g.
eloquent for Laravel, symfony-workflow for Doctrine, framework-native skill for Prisma / TypeORM / SQLAlchemy / GORM / Diesel)
- Creating migrations only — use the framework-specific migration skill (
laravel-migration for Laravel, framework-native for others)
Procedure: Optimize a query
Step 0: Inspect
- Read project docs in
agents/reference/docs/ for database architecture.
- Check
config/database.php for connection definitions.
- Detect engine: check
.env driver and docker-compose.yml.
Step 1: Diagnose
Run EXPLAIN / EXPLAIN ANALYZE:
EXPLAIN ANALYZE SELECT * FROM projects WHERE customer_id = 42 AND status = 'active';
Check for: full table scans (type=ALL), missing indexes (key=NULL), filesort, temporary tables.
Step 2: Fix
- Add missing indexes (most selective column first in composites)
- Rewrite anti-patterns (subquery → JOIN,
OFFSET → cursor, SELECT * → specific columns)
- Add eager loading for N+1 queries
- Always paginate list endpoints
Step 3: Verify
Re-run EXPLAIN and confirm improved plan.
Schema awareness (anti-hallucination)
Never guess table or column names. Verify before writing queries/migrations:
- Read migrations — source of truth
- Read models —
$table, $connection, $fillable, $casts, relationships
- Run schema queries — use the project's REPL or a raw introspection query:
- Laravel:
php artisan tinker --execute="Schema::getColumnListing('table')"
- Symfony / Doctrine:
bin/console doctrine:mapping:info
- Rails:
bin/rails runner "p ActiveRecord::Base.connection.columns('table').map(&:name)"
- Prisma:
npx prisma db pull --print | grep -A20 "model Table"
- Generic SQL:
psql -d mydb -c "\d table" / mysql -e "DESCRIBE table"
- Check project docs —
agents/reference/docs/ for conventions
| Trap |
Reality |
| Assuming column exists |
Check migration/model first |
| Wrong table prefix |
Customer tables may have prefixes |
| Wrong connection |
api_database vs customer_database — verify |
| Inventing pivot tables |
Check if they actually exist |
Dump to the Evidence Report (source-discovery)
When the task touches schema-driven work, this dump is the DB surface of the
source-discovery discipline. Record it to the
gitignored session cache with provenance, framework-neutral (MySQL / Postgres /
SQLite; ORM-agnostic):
- Capture tables, columns, types, primary/foreign/unique keys, indexes,
relations, and the derived filter/sort/group-ability — each with
observed_at / source (migration:line or the introspection command).
- In-codebase = local, read fresh, no card. A schema defined by repo
migrations / models / ORM / app code (including schemaless stores the app
controls — Mongoose / Prisma / Firestore rules) is always resolved locally and
re-read each task. The migration is intended truth, the live DB is actual —
surface any divergence as a drift signal.
- Only negative facts graduate to a committed card (
agents/knowledge/):
"searched, column/table does not exist" after an exhausted search. Positive
structure stays in the session Evidence Report, re-read fresh — never a card.
- A DB-not-in-codebase (vendor SaaS / partner / legacy, schema not in the
repo and not app-controlled) is the only DB that may be card-worthy.
Conventions
→ See guideline php/database.md for indexing, transactions, migrations, multi-connection patterns.
Output format
- Migration file or query change with EXPLAIN analysis
- Index recommendations with rationale
Gotcha
MySQL and MariaDB share a query-syntax world. They never share a migration
one. Treat them as one engine for writing a SELECT, and as two engines for
everything that changes a schema or reports on a plan: online-DDL semantics,
lock behavior under ALTER, feature availability, and EXPLAIN output all
diverge. Read the engine and its version from the project before making any
claim in that second group; where the project does not declare one, say the
engine is unknown rather than assuming.
- Check existing indexes before adding — duplicates waste write performance.
- Consider multi-tenant implications — queries may need customer DB scoping.
EXPLAIN output varies between MariaDB and MySQL.
- Don't use
TEXT in WHERE without prefix index.
Do NOT
- Do NOT guess table/column names — verify against migrations or models first.
- Do NOT add indexes without checking existing ones — duplicates waste write performance.
- Do NOT use
float for money — use decimal.
Auto-trigger keywords
- database
- MariaDB
- MySQL
- migration
- indexing
- query optimization
1---2name: database3description: Use when working with database architecture, MariaDB/MySQL tuning, indexing strategies, slow queries, or multi-connection patterns — even when the user just says 'this query is slow'.4---56# database789> **Grounded corpus (Tier-1 consultation):** symptom → index/strategy10> decisions come grounded — `./scripts-run11> <skills-root>/corpus-grounding/scripts/ground search --manifest12> <skills-root>/database/data/manifest.json "<symptom>"` returns root13> cause, strategy, good-code sketch, anti-pattern, and the verification14> probe (EXPLAIN expectation). Corpus:15> [`data/query-tuning.csv`](data/query-tuning.csv) (PostgreSQL 16 /16> MySQL 8-derived).1718## When to use1920Use when designing schemas, optimizing queries, adding indexes, or troubleshooting database performance.2122Do NOT use when:23- Writing framework-specific ORM models (use the matching skill — e.g. `eloquent` for Laravel, `symfony-workflow` for Doctrine, framework-native skill for Prisma / TypeORM / SQLAlchemy / GORM / Diesel)24- Creating migrations only — use the framework-specific migration skill (`laravel-migration` for Laravel, framework-native for others)2526## Procedure: Optimize a query2728### Step 0: Inspect29301. Read project docs in `agents/reference/docs/` for database architecture.312. Check `config/database.php` for connection definitions.323. Detect engine: check `.env` driver and `docker-compose.yml`.3334### Step 1: Diagnose3536Run `EXPLAIN` / `EXPLAIN ANALYZE`:3738```sql39EXPLAIN ANALYZE SELECT * FROM projects WHERE customer_id = 42 AND status = 'active';40```4142Check for: full table scans (`type=ALL`), missing indexes (`key=NULL`), filesort, temporary tables.4344### Step 2: Fix4546- Add missing indexes (most selective column first in composites)47- Rewrite anti-patterns (subquery → JOIN, `OFFSET` → cursor, `SELECT *` → specific columns)48- Add eager loading for N+1 queries49- Always paginate list endpoints5051### Step 3: Verify5253Re-run `EXPLAIN` and confirm improved plan.5455## Schema awareness (anti-hallucination)5657**Never guess table or column names.** Verify before writing queries/migrations:58591. **Read migrations** — source of truth602. **Read models** — `$table`, `$connection`, `$fillable`, `$casts`, relationships613. **Run schema queries** — use the project's REPL or a raw introspection query:62 - Laravel: `php artisan tinker --execute="Schema::getColumnListing('table')"`63 - Symfony / Doctrine: `bin/console doctrine:mapping:info`64 - Rails: `bin/rails runner "p ActiveRecord::Base.connection.columns('table').map(&:name)"`65 - Prisma: `npx prisma db pull --print | grep -A20 "model Table"`66 - Generic SQL: `psql -d mydb -c "\d table"` / `mysql -e "DESCRIBE table"`674. **Check project docs** — `agents/reference/docs/` for conventions6869| Trap | Reality |70|---|---|71| Assuming column exists | Check migration/model first |72| Wrong table prefix | Customer tables may have prefixes |73| Wrong connection | `api_database` vs `customer_database` — verify |74| Inventing pivot tables | Check if they actually exist |7576### Dump to the Evidence Report (source-discovery)7778When the task touches schema-driven work, this dump **is** the DB surface of the79[`source-discovery`](../source-discovery/SKILL.md) discipline. Record it to the80gitignored session cache with provenance, framework-neutral (MySQL / Postgres /81SQLite; ORM-agnostic):8283- Capture **tables, columns, types, primary/foreign/unique keys, indexes,84 relations**, and the derived **filter/sort/group-ability** — each with85 `observed_at` / `source` (`migration:line` or the introspection command).86- **In-codebase = local, read fresh, no card.** A schema defined by repo87 migrations / models / ORM / app code (including schemaless stores the app88 controls — Mongoose / Prisma / Firestore rules) is always resolved locally and89 re-read each task. The **migration is intended truth, the live DB is actual** —90 surface any divergence as a drift signal.91- **Only negative facts graduate to a committed card** (`agents/knowledge/`):92 "searched, column/table does not exist" after an exhausted search. Positive93 structure stays in the session Evidence Report, re-read fresh — never a card.94- A **DB-not-in-codebase** (vendor SaaS / partner / legacy, schema not in the95 repo and not app-controlled) is the only DB that may be card-worthy.9697## Conventions9899→ See guideline `php/database.md` for indexing, transactions, migrations, multi-connection patterns.100101## Output format1021031. Migration file or query change with EXPLAIN analysis1042. Index recommendations with rationale105106## Gotcha107108**MySQL and MariaDB share a query-syntax world. They never share a migration109one.** Treat them as one engine for writing a `SELECT`, and as two engines for110everything that changes a schema or reports on a plan: online-DDL semantics,111lock behavior under `ALTER`, feature availability, and `EXPLAIN` output all112diverge. Read the engine and its version from the project before making any113claim in that second group; where the project does not declare one, say the114engine is unknown rather than assuming.115116- Check existing indexes before adding — duplicates waste write performance.117- Consider multi-tenant implications — queries may need customer DB scoping.118- `EXPLAIN` output varies between MariaDB and MySQL.119- Don't use `TEXT` in WHERE without prefix index.120121## Do NOT122123- Do NOT guess table/column names — verify against migrations or models first.124- Do NOT add indexes without checking existing ones — duplicates waste write performance.125- Do NOT use `float` for money — use `decimal`.126127## Auto-trigger keywords128129- database130- MariaDB131- MySQL132- migration133- indexing134- query optimization