MySQL remains the database under a staggering share of the web, and Azure MySQL Flexible Server is its managed home on Azure. The shape mirrors yesterday’s PostgreSQL post, same tiers, same VNet options, same HA model, so today I will move faster on the shared ground and slower on what is MySQL specific.
The Shared Foundation, Quickly
Burstable for dev, General Purpose for most production, Business Critical for latency sensitive or high concurrency workloads. Delegated subnet or private endpoint networking, public access off, Entra authentication on. Zone redundant HA gives a synchronous standby in a second zone with automatic failover; same zone HA exists for regions without zones or workloads that want local standby without cross zone write latency. Autogrow on, 35 day backups with geo redundancy, maintenance window pinned to your quietest hour. All the arguments from yesterday transfer directly.
resource "azurerm_mysql_flexible_server" "this" {
name = "mysql-shop-prod"
resource_group_name = azurerm_resource_group.data.name
location = "westeurope"
version = "8.0.21"
sku_name = "GP_Standard_D4ds_v4"
delegated_subnet_id = azurerm_subnet.mysql.id
private_dns_zone_id = azurerm_private_dns_zone.mysql.id
backup_retention_days = 35
geo_redundant_backup_enabled = true
zone = "1"
storage {
size_gb = 256
auto_grow_enabled = true
iops = 2000
log_on_disk_enabled = true
}
high_availability {
mode = "ZoneRedundant"
standby_availability_zone = "2"
}
}
resource "azurerm_mysql_flexible_server_configuration" "slow_log" {
name = "slow_query_log"
resource_group_name = azurerm_resource_group.data.name
server_name = azurerm_mysql_flexible_server.this.name
value = "ON"
}
resource "azurerm_mysql_flexible_server_configuration" "long_query" {
name = "long_query_time"
resource_group_name = azurerm_resource_group.data.name
server_name = azurerm_mysql_flexible_server.this.name
value = "0.5"
}
MySQL Specific Performance Levers
InnoDB buffer pool size is the parameter that matters most and it is preset per SKU, which is the hidden reason scaling up often fixes performance: you bought buffer pool. Before scaling, check the hit rate; if reads are missing the pool and the working set genuinely exceeds memory, scale, otherwise fix the queries. The accelerated logs feature on Business Critical (log_on_disk settings vary by tier) moves redo logging to faster storage and measurably lifts write throughput for commit heavy workloads, effectively free performance, enable it where offered. Connection handling differs from Postgres in a friendly way, MySQL threads are cheaper than Postgres processes, but the ecosystem habit of enormous application pool sizes still bites; size pools to what the server tier supports and let queueing happen in the app, not as max_connections errors. And temporary table spill to disk is the classic silent killer for reporting queries, watch created_tmp_disk_tables and raise tmp_table_size and max_heap_table_size judiciously when sorted or grouped result sets exceed memory.
Replicas, GTID, and the Upgrade Path
Read replicas (up to ten, async, in region or cross region) carry the usual uses: read scaling, reporting isolation, and DR by promotion. GTID based replication underpins them, which pays off in clean promotion semantics, and replica lag is your metric to alert on, lag during business hours usually traces to large transactions or missing indexes on the replica applied workload. For DR, a cross region replica plus a rehearsed promotion runbook is the standard pattern, and after promotion your application config flip should be DNS or App Configuration driven rather than a redeploy. Version upgrades: 5.7 to 8.0 was the industry’s great migration and if any 5.7 still lurks in your estate, it is running on borrowed time everywhere, not just Azure; test with a replica upgraded ahead of the primary. Slow query log to Log Analytics, weekly review of the top offenders, same discipline as yesterday, because databases reward routines more than heroics.
Cheers
Osama
Leave a comment