Category: Databases

  • MySQL Performance for Beginners: Right-size InnoDB Cache and Manage Binary Logging

    MySQL Performance for Beginners: Right-size InnoDB Cache and Manage Binary Logging

    If your MySQL server feels slow, chances are your hot data or indexes don’t fit in memory, or your logging is forcing too many disk syncs. This beginner-friendly guide shows how to size InnoDB’s cache based on what you actually read, which other caches matter, and how binary logging affects performance.

    1) Right-size the InnoDB buffer pool using index size and hot data
    Goal: fit your “working set” in memory. That working set is:

    • Index pages of the most-read tables
    • Data pages for the most-read rows
    • Plus some headroom for change buffering, temp pages, and concurrency

    Step A: Measure index sizes

    • Per table:
      SELECT table_schema, table_name,
      ROUND(index_length/1024/1024, 1) AS index_mb,
      ROUND(data_length/1024/1024, 1) AS data_mb
      FROM information_schema.tables
      WHERE engine = 'InnoDB'
      AND table_schema NOT IN ('mysql','sys','performance_schema','information_schema')
      ORDER BY index_length DESC;
    • Per schema:
      SELECT table_schema,
      ROUND(SUM(index_length)/1024/1024, 1) AS total_index_mb,
      ROUND(SUM(data_length)/1024/1024, 1) AS total_data_mb
      FROM information_schema.tables
      WHERE engine = 'InnoDB'
      GROUP BY table_schema
      ORDER BY total_index_mb DESC;

    Step B: Find your “most-read” tables
    If you have the sys schema (MySQL 5.7+/8.0):

    • Top tables by reads:
      SELECT table_schema, table_name, rows_fetched
      FROM sys.schema_table_statistics
      ORDER BY rows_fetched DESC
      LIMIT 20;

    Alternative with performance_schema:

    • Top tables by read time:
      SELECT OBJECT_SCHEMA AS table_schema, OBJECT_NAME AS table_name, SUM_TIMER_READ
      FROM performance_schema.table_io_waits_summary_by_table
      WHERE OBJECT_SCHEMA NOT IN ('mysql','sys','performance_schema','information_schema')
      ORDER BY SUM_TIMER_READ DESC
      LIMIT 20;

    Step C: Estimate the “hot set”
    Join the hot tables from Step B to information_schema.tables to sum index and data sizes for just those hot tables:

    WITH hot AS (
    SELECT s.table_schema, s.table_name
    FROM sys.schema_table_statistics AS s
    ORDER BY s.rows_fetched DESC
    LIMIT 20
    )
    SELECT
    ROUND(SUM(t.index_length)/1024/1024/1024, 1) AS hot_index_gb,
    ROUND(SUM(t.data_length)/1024/1024/1024, 1) AS hot_data_gb
    FROM information_schema.tables t
    JOIN hot h
    ON t.table_schema = h.table_schema AND t.table_name = h.table_name
    WHERE t.engine = 'InnoDB';

    This gives a practical target for what should be memory-resident most of the time.

    Step D: Choose a buffer pool size

    • Dedicated DB server: make innodb_buffer_pool_size roughly 60–75% of RAM.
    • If RAM is limited, aim to fit at least the hot indexes plus a healthy portion of hot data. A simple rule: target 1.2x to 1.5x the hot_index + hot_data estimate, capped by the 60–75% guideline.
    • Example: hot_index = 20 GB and hot_data = 15 GB → target 42–52 GB; with a 64 GB server, set innodb_buffer_pool_size to ~48 GB.

    Also configure:

    • innodb_buffer_pool_instances: 4–8 instances for pools > 8–16 GB (MySQL 8.0 auto-scales, but setting 4–8 avoids contention on large pools).
    • Enable warmup:
    • innodb_buffer_pool_dump_at_shutdown = ON
    • innodb_buffer_pool_load_at_startup = ON

    Step E: Verify it worked

    • Buffer pool hit ratio (aim for > 99% on read-heavy workloads):
      SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';
      Hit ratio ≈ 1 – (Innodb_buffer_pool_reads / Innodb_buffer_pool_read_requests)
    • Check disk reads dropping after increasing the pool.
    • Use sys.schema_table_statistics_with_buffer to see buffer hit rates by table.

    Notes and caveats:

    • information_schema sizes are estimates; compressed and dynamic row formats vary.
    • Growth and workload change over time—revisit quarterly.

    2) Other caching settings that help
    Beyond the buffer pool, these settings reduce CPU and I/O churn:

    • InnoDB redo log buffer
    • innodb_log_buffer_size: if you run large or bursty transactions, increase to 64–256 MB to reduce log writes mid-transaction.
    • Adaptive Hash Index (AHI)
    • innodb_adaptive_hash_index = ON by default. It can speed up repeated equality lookups on the same index keys. In highly concurrent or non-uniform workloads it can add contention; test ON vs OFF with real workload.
    • Change buffering (secondary index change cache)
    • innodb_change_buffering = all (default) helps insert/update workloads on secondary indexes by deferring disk IO. For read-mostly workloads, leave it ON; for write-heavy with large memory you can test off, but most benefit from leaving it on.
    • Table and definition cache
    • table_open_cache and table_definition_cache: prevent re-opening tables. If Opened_tables or Opened_table_definitions grows quickly under steady load, raise these. Typical ranges: thousands to tens of thousands on busy servers.
    • table_open_cache_instances: multiple instances reduce lock contention on the cache.
    • Thread cache
    • thread_cache_size: if Threads_created increases rapidly, raise this so connections reuse threads.
    • OS caching and double buffering
    • innodb_flush_method = O_DIRECT (Linux) to avoid double-buffering in the OS page cache, letting the buffer pool be the primary cache.
    • Put data, redo logs, and binlogs on fast, separate storage if possible.
    • Query cache (only MySQL 5.7 and earlier)
    • Disable it (query_cache_type=0, query_cache_size=0). It harms concurrency; MySQL 8.0 removed it.
    • Application-level caching
    • Use connection pooling and prepared statements in your driver.
    • Cache hot result sets in your app or a cache layer (Redis/Memcached) to avoid unnecessary reads.

    3) Binary logging and performance: what to know
    What binary logging does:

    • Records every committed transaction in the binary log for replication and point-in-time recovery (PITR).
    • Adds CPU work to format events and extra I/O to persist them. The fsync frequency is controlled by sync_binlog.

    Performance impact:

    • Additional write and fsync on each commit (especially with sync_binlog = 1).
    • Extra memory for per-session binlog caches; potential temp files if caches overflow.
    • Overhead varies widely by workload; OLTP with many small commits feels it most.

    If you keep binlog enabled (recommended for most production):

    • Keep durability but reduce overhead
    • sync_binlog = 1 for maximum safety; consider values > 1 (e.g., 100) to batch fsyncs if you can risk losing up to N transactions on OS crash.
    • Pair with innodb_flush_log_at_trx_commit:
      • 1: safest (fsync redo on every commit).
      • 2: fsync redo once per second; may lose up to 1 second of data on crash.
    • binlog_row_image = MINIMAL to log only changed columns, shrinking binlog size.
    • binlog_transaction_compression = ON (MySQL 8.0) to compress row events.
    • binlog_cache_size and binlog_stmt_cache_size large enough to keep most transactions in memory (e.g., 1–16 MB) to avoid temp files.
    • Place binlogs on fast storage separate from data/redo to isolate I/O.
    • Consider group-commit tuning:
      • binlog_group_commit_sync_delay (microseconds) and binlog_group_commit_sync_no_delay_count can batch commits, trading tiny latency for throughput.

    Advantages of disabling binary logging:

    • Lower transaction latency and higher throughput on write-heavy OLTP by removing binlog writes and fsyncs.
    • Less disk usage and IO on the server.
    • Less CPU spent formatting and compressing binlog events.
    • Smaller memory footprint per connection (binlog caches).

    Disadvantages of disabling binary logging:

    • You lose replication. You cannot create replicas or use Group Replication/InnoDB Cluster/async replicas later without reconfiguring and taking a fresh snapshot.
    • You lose point-in-time recovery. Backups can only restore to the snapshot time; no replay of changes since the last backup.
    • You lose certain auditing/troubleshooting options that rely on reading binlogs.
    • GTIDs cannot be used; some tooling and operational patterns depend on them.

    When is disabling reasonable?

    • Single-instance dev/test or ephemeral environments.
    • Simple read-mostly systems where you accept snapshot-only backups and can rebuild from source of truth.
    • Even then, consider keeping binlog ON and tuning sync_binlog for a safer middle ground.

    Practical checklist

    • Measure hot tables and estimate hot indexes + hot data.
    • Size innodb_buffer_pool_size to fit the hot set, within 60–75% of RAM on a dedicated server.
    • Enable buffer pool dump/load to warm caches on restart.
    • Tune table_open_cache, thread_cache_size, and innodb_log_buffer_size based on observed metrics.
    • Use O_DIRECT to avoid double buffering.
    • Keep binlog ON for most production setups; tune sync_binlog, row image, and compression; put binlogs on fast storage.
    • Consider disabling binlog only when you explicitly do not need replication or PITR.

    Always make one change at a time, monitor with real workload, and compare throughput, latency, and disk IO before and after.