Optimizing FiveM Databases: Fixing MySQL Lag with oxmysql Indexing and Slow-Query Logs

Optimizing FiveM Databases: Fixing MySQL Lag with oxmysql Indexing and Slow-Query Logs

When server administrators notice hitching or frame drops, the immediate assumption is often that a resource has an inefficient tick handler or that the server host lacks processing power. In many instances, the real issue originates in the database layer. Every character login, item move, vehicle spawn, and bank transaction executes a database request. When these queries take too long or lock resources, the server responds with noticeable hitches. This guide details how database performance impacts your FiveM server and provides actionable methods to resolve common database bottlenecks.

How Database Lag Causes Server Stutters

FiveM server resources execute their core logic on a central thread. When a script runs a database operation and forces the thread to pause until a response arrives, the whole server freezes for that duration. A query taking a few hundred milliseconds might seem trivial on its own, but it delays the entire tick loop for every connected player.

Because of this architecture, database delays rarely display clear database errors. Instead, they produce symptoms such as:

Throwing more CPU hardware at your server will not resolve these bottlenecks. The effective solution requires reducing query execution times and preventing database operations from stalling the main thread.

Sync vs Async: Keeping Database Calls off the Main Thread

The popular database library oxmysql provides both synchronous and asynchronous execution methods. Choosing between these modes is one of the most critical decisions for maintaining overall server responsiveness.

Synchronous methods (such as using .await or synchronous helper functions) suspend the running thread until the database returns a response. If used improperly, especially inside frequent events or iterative loops, synchronous execution brings server ticks to a complete standstill.

Asynchronous methods (using callbacks or non-blocking promises) allow the server tick to proceed while the database processes the query in the background. Once the operation finishes, the result is returned to the script without halting active gameplay.

As a standard practice, always default to asynchronous requests. Reserve synchronous queries exclusively for initial resource startup routines where data must be loaded before dependent scripts can run. Never place synchronous requests inside events that trigger regularly or within loop structures. A single misplaced .await call inside a frequently executed path remains a primary driver of server hitching.

Adding Indexes to High-Traffic Columns

Adding targeted indexes is one of the quickest ways to improve query response times. An index creates a lookup structure that enables MySQL to locate specific records immediately rather than scanning every single row in a table. Without indexes, searching for a character by identifier requires reading through every entry in the database, which causes severe delays as table sizes grow.

Focus indexing efforts on columns that your scripts search frequently. For QBCore scripts, indexing citizenid is essential. For ESX scripts, index identifier. Additionally, index owner fields across vehicle and housing tables, plate columns, and player identifiers inside inventory databases.

-- Create indexes to speed up player lookups
ALTER TABLE player_vehicles ADD INDEX idx_citizenid (citizenid);
ALTER TABLE owned_vehicles  ADD INDEX idx_owner (owner);

Be cautious to avoid two common pitfalls: missing indexes on columns used in WHERE or JOIN statements (which force full table scans) and over-indexing unnecessary columns. Excess indexes increase write overhead on every insert or update. Index only the columns regularly referenced in conditional logic.

How to Use the MySQL Slow Query Log

Rather than guessing which resource causes database strain, let MySQL record problem queries directly using the slow query log. This logging feature records any query exceeding a specific execution threshold.

Configure your my.cnf or my.ini file with the following directives:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 0.5
log_queries_not_using_indexes = 1

Setting long_query_time to 0.5 seconds (or 0.1 seconds during dedicated testing) alongside log_queries_not_using_indexes will quickly surface unindexed lookups. Run your server during normal gameplay and inspect the output file. Examine frequently logged statements and use the EXPLAIN command in your database administration tool to confirm whether the queries use available indexes.

Eliminating SELECT * and N+1 Loop Queries

Two coding patterns routinely impair server efficiency across custom and public scripts.

The first issue is using SELECT * to retrieve every column when only specific fields are required. Requesting unnecessary columns, such as large inventory JSON blocks or extra character details, wastes bandwidth and processing memory. Always specify the required fields explicitly, such as SELECT money, job FROM players WHERE ....

The second and more damaging pattern is the N+1 query loop. This occurs when a script iterates over a list and fires an individual query for every item in that list:

-- INEFFICIENT: Executes a separate query for each vehicle
for _, id in ipairs(ids) do
    MySQL.query.await('SELECT plate FROM player_vehicles WHERE id = ?', { id })
end

Querying twenty vehicles in this manner creates twenty individual round trips to the database. You can consolidate this work into a single request using the IN clause:

-- EFFICIENT: Fetches all matching records in one request
MySQL.query('SELECT id, plate FROM player_vehicles WHERE id IN (?)', { ids })

Apply this batching strategy to save routines as well. Rather than updating individual player records using separate queries inside a loop, use oxmysql batch inserts or transactions to handle multiple updates in a single query execution.

Connection Pools, Persistence Timers, and MariaDB Memory Settings

Server-level configurations also play a major role in database responsiveness:

Identifying Bottlenecks and Next Steps

Before refactoring your resources, measure performance to identify exact database usage per resource. Utilizing profiling tools like those available at 0resmon-tebex.io helps pinpoint which scripts generate the highest query volumes. When selecting new resources for your server, choose well-architected scripts from sources like scripts-tebex.io, or specialized QBCore options designed with proper index structures at qb-tebex.io. By configuring slow-query logging, creating indexes on key columns, batching write operations, and keeping queries off the main thread, you can maintain optimal server frame rates even under heavy player loads.

More FiveM script guides

Fixing FiveM Server Boot Failures: Triage and Bisection Guide
Guide
Fixing FiveM Server Boot Failures: Triage and Bisection Guide
Running Multiple FiveM Servers: Dev, Main and Event Instances Without Doubling Your Workload
Guide
Running Multiple FiveM Servers: Dev, Main and Event Instances Without Doubling Your Workload
Mastering FiveM Weather and Time Sync for City Immersion
Guide
Mastering FiveM Weather and Time Sync for City Immersion
Guide published · Jun 23, 2026 Browse every FiveM article →