Files
ThordekkCore/sql/playerbot/02_query_optimization.sql
T
2026-01-20 21:33:16 -03:00

415 lines
14 KiB
SQL

-- =====================================================
-- QUERY OPTIMIZATION PATTERNS FOR PLAYERBOT
-- Target: <10ms query response, batch operations
-- =====================================================
USE `characters`;
DELIMITER $$
-- =====================================================
-- OPTIMIZED BOT LOGIN QUERIES
-- =====================================================
-- Single bot login with all required data in one query
CREATE PROCEDURE `sp_bot_login_optimized`(
IN p_guid INT UNSIGNED,
OUT p_success BOOLEAN
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
SET p_success = FALSE;
ROLLBACK;
END;
SET p_success = FALSE;
START TRANSACTION;
-- Single query to get all character data using covering index
SELECT
c.guid, c.account, c.name, c.race, c.class, c.gender, c.level,
c.xp, c.money, c.skin, c.face, c.hairStyle, c.hairColor, c.facialStyle,
c.zone, c.map, c.position_x, c.position_y, c.position_z, c.orientation,
c.taximask, c.cinematic, c.totaltime, c.leveltime, c.extra_flags,
c.stable_slots, c.at_login, c.death_expire_time,
cs.maxhealth, cs.maxpower1, cs.maxpower2, cs.maxpower3, cs.maxpower4,
cs.maxpower5, cs.maxpower6, cs.maxpower7,
cs.strength, cs.agility, cs.stamina, cs.intellect, cs.spirit,
cs.armor, cs.resHoly, cs.resFire, cs.resNature, cs.resFrost,
cs.resShadow, cs.resArcane, cs.blockPct, cs.dodgePct, cs.parryPct,
cs.critPct, cs.rangedCritPct, cs.spellCritPct,
cs.attackPower, cs.rangedAttackPower, cs.spellPower
INTO @guid, @account, @name, @race, @class, @gender, @level,
@xp, @money, @skin, @face, @hairStyle, @hairColor, @facialStyle,
@zone, @map, @position_x, @position_y, @position_z, @orientation,
@taximask, @cinematic, @totaltime, @leveltime, @extra_flags,
@stable_slots, @at_login, @death_expire_time,
@maxhealth, @maxpower1, @maxpower2, @maxpower3, @maxpower4,
@maxpower5, @maxpower6, @maxpower7,
@strength, @agility, @stamina, @intellect, @spirit,
@armor, @resHoly, @resFire, @resNature, @resFrost,
@resShadow, @resArcane, @blockPct, @dodgePct, @parryPct,
@critPct, @rangedCritPct, @spellCritPct,
@attackPower, @rangedAttackPower, @spellPower
FROM characters c
STRAIGHT_JOIN character_stats cs ON c.guid = cs.guid
WHERE c.guid = p_guid
LIMIT 1;
IF @guid IS NOT NULL THEN
-- Update online status
UPDATE characters SET online = 1 WHERE guid = p_guid;
-- Update session cache
INSERT INTO playerbot_session_cache
(guid, account, name, level, class, race, zone, map, online, last_action)
VALUES
(p_guid, @account, @name, @level, @class, @race, @zone, @map, 1, NOW())
ON DUPLICATE KEY UPDATE
online = 1,
last_action = NOW();
SET p_success = TRUE;
END IF;
COMMIT;
END$$
-- =====================================================
-- BATCH BOT OPERATIONS
-- =====================================================
-- Batch load multiple bots (replaces N+1 queries)
CREATE PROCEDURE `sp_batch_load_bots`(
IN p_account_id INT UNSIGNED,
IN p_limit INT UNSIGNED
)
BEGIN
-- Use temporary table for batch processing
CREATE TEMPORARY TABLE IF NOT EXISTS temp_bot_batch (
guid INT UNSIGNED PRIMARY KEY,
loaded BOOLEAN DEFAULT FALSE
);
-- Select bots to load
INSERT INTO temp_bot_batch (guid)
SELECT guid FROM characters
WHERE account = p_account_id AND online = 0
LIMIT p_limit;
-- Batch load all character data
SELECT
c.guid, c.account, c.name, c.race, c.class, c.level,
c.zone, c.map, c.position_x, c.position_y, c.position_z,
cs.maxhealth, cs.maxpower1, cs.strength, cs.agility, cs.stamina,
cs.intellect, cs.spirit, cs.armor, cs.attackPower, cs.spellPower
FROM characters c
INNER JOIN temp_bot_batch t ON c.guid = t.guid
INNER JOIN character_stats cs ON c.guid = cs.guid;
-- Batch load equipment
SELECT
ci.guid, ci.slot, ci.item, ii.itemEntry, ii.enchantments
FROM character_inventory ci
INNER JOIN temp_bot_batch t ON ci.guid = t.guid
INNER JOIN item_instance ii ON ci.item = ii.guid
WHERE ci.bag = 0 AND ci.slot < 19;
-- Batch load action bars
SELECT
ca.guid, ca.button, ca.action, ca.type
FROM character_action ca
INNER JOIN temp_bot_batch t ON ca.guid = t.guid
WHERE ca.spec = 0;
-- Batch load spells
SELECT
cs.guid, cs.spell
FROM character_spell cs
INNER JOIN temp_bot_batch t ON cs.guid = t.guid
WHERE cs.active = 1 AND cs.disabled = 0;
-- Batch update online status
UPDATE characters c
INNER JOIN temp_bot_batch t ON c.guid = t.guid
SET c.online = 1;
-- Batch insert into session cache
INSERT INTO playerbot_session_cache
(guid, account, name, level, class, race, zone, map, online, last_action)
SELECT
c.guid, c.account, c.name, c.level, c.class, c.race,
c.zone, c.map, 1, NOW()
FROM characters c
INNER JOIN temp_bot_batch t ON c.guid = t.guid
ON DUPLICATE KEY UPDATE
online = 1,
last_action = NOW();
DROP TEMPORARY TABLE temp_bot_batch;
END$$
-- =====================================================
-- PREPARED STATEMENT PATTERNS
-- =====================================================
-- Create prepared statements for frequent queries
CREATE PROCEDURE `sp_init_prepared_statements`()
BEGIN
-- Bot position update
SET @sql_update_position = 'UPDATE characters SET position_x = ?, position_y = ?, position_z = ?, orientation = ?, map = ?, zone = ? WHERE guid = ?';
PREPARE stmt_update_position FROM @sql_update_position;
-- Bot health/power update
SET @sql_update_health = 'UPDATE character_stats SET health = ?, power1 = ?, power2 = ? WHERE guid = ?';
PREPARE stmt_update_health FROM @sql_update_health;
-- Bot aura update
SET @sql_update_aura = 'INSERT INTO character_aura (guid, caster_guid, spell, effect_mask, recalculate_mask, stackcount, maxduration, remaintime, remaincharges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE remaintime = VALUES(remaintime), stackcount = VALUES(stackcount)';
PREPARE stmt_update_aura FROM @sql_update_aura;
-- Bot combat state
SET @sql_update_combat = 'UPDATE playerbot_state SET combat_state = ?, ai_state = ?, last_update = NOW() WHERE guid = ?';
PREPARE stmt_update_combat FROM @sql_update_combat;
END$$
-- =====================================================
-- OPTIMIZED SEARCH QUERIES
-- =====================================================
-- Find nearby bots (spatial query optimization)
CREATE PROCEDURE `sp_find_nearby_bots`(
IN p_x FLOAT,
IN p_y FLOAT,
IN p_z FLOAT,
IN p_map INT,
IN p_distance FLOAT,
IN p_limit INT
)
BEGIN
-- Use bounding box pre-filter for performance
SET @min_x = p_x - p_distance;
SET @max_x = p_x + p_distance;
SET @min_y = p_y - p_distance;
SET @max_y = p_y + p_distance;
SELECT
c.guid, c.name, c.level, c.class,
SQRT(POW(c.position_x - p_x, 2) + POW(c.position_y - p_y, 2) + POW(c.position_z - p_z, 2)) AS distance
FROM characters c USE INDEX (idx_zone_map_position)
WHERE c.map = p_map
AND c.online = 1
AND c.position_x BETWEEN @min_x AND @max_x
AND c.position_y BETWEEN @min_y AND @max_y
HAVING distance <= p_distance
ORDER BY distance
LIMIT p_limit;
END$$
-- Find bots by criteria (optimized filtering)
CREATE PROCEDURE `sp_find_bots_by_criteria`(
IN p_min_level INT,
IN p_max_level INT,
IN p_class INT,
IN p_zone INT,
IN p_online_only BOOLEAN,
IN p_limit INT
)
BEGIN
SELECT
c.guid, c.name, c.level, c.class, c.race, c.zone, c.online
FROM characters c USE INDEX (idx_bot_selection)
WHERE
(p_min_level IS NULL OR c.level >= p_min_level)
AND (p_max_level IS NULL OR c.level <= p_max_level)
AND (p_class IS NULL OR c.class = p_class)
AND (p_zone IS NULL OR c.zone = p_zone)
AND (NOT p_online_only OR c.online = 1)
LIMIT p_limit;
END$$
-- =====================================================
-- BULK UPDATE OPERATIONS
-- =====================================================
-- Bulk save bot states (replaces individual updates)
CREATE PROCEDURE `sp_bulk_save_bot_states`(
IN p_state_data JSON
)
BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
-- Create temporary table from JSON data
CREATE TEMPORARY TABLE temp_bot_states (
guid INT UNSIGNED PRIMARY KEY,
position_x FLOAT,
position_y FLOAT,
position_z FLOAT,
orientation FLOAT,
map INT,
zone INT,
health INT,
power INT,
online TINYINT
);
-- Parse JSON and insert into temp table
INSERT INTO temp_bot_states
SELECT
JSON_UNQUOTE(JSON_EXTRACT(value, '$.guid')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.position_x')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.position_y')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.position_z')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.orientation')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.map')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.zone')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.health')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.power')),
JSON_UNQUOTE(JSON_EXTRACT(value, '$.online'))
FROM JSON_TABLE(p_state_data, '$[*]' COLUMNS (value JSON PATH '$')) AS jt;
-- Bulk update characters table
UPDATE characters c
INNER JOIN temp_bot_states t ON c.guid = t.guid
SET
c.position_x = t.position_x,
c.position_y = t.position_y,
c.position_z = t.position_z,
c.orientation = t.orientation,
c.map = t.map,
c.zone = t.zone,
c.online = t.online;
-- Bulk update character_stats
UPDATE character_stats cs
INNER JOIN temp_bot_states t ON cs.guid = t.guid
SET
cs.health = t.health,
cs.power1 = t.power;
-- Bulk update playerbot_state
INSERT INTO playerbot_state (guid, online, position_x, position_y, position_z, map, zone, last_update)
SELECT guid, online, position_x, position_y, position_z, map, zone, NOW()
FROM temp_bot_states
ON DUPLICATE KEY UPDATE
online = VALUES(online),
position_x = VALUES(position_x),
position_y = VALUES(position_y),
position_z = VALUES(position_z),
map = VALUES(map),
zone = VALUES(zone),
last_update = NOW();
DROP TEMPORARY TABLE temp_bot_states;
COMMIT;
END$$
-- =====================================================
-- CACHE MANAGEMENT
-- =====================================================
-- Preload frequently accessed bots into cache
CREATE PROCEDURE `sp_preload_bot_cache`(
IN p_account_id INT UNSIGNED
)
BEGIN
-- Clear old cache entries
DELETE FROM playerbot_session_cache
WHERE last_action < DATE_SUB(NOW(), INTERVAL 1 HOUR);
-- Load active bots into cache
INSERT IGNORE INTO playerbot_session_cache
(guid, account, name, level, class, race, zone, map, online, group_id, last_action)
SELECT
c.guid, c.account, c.name, c.level, c.class, c.race,
c.zone, c.map, c.online, IFNULL(gm.guid, 0), NOW()
FROM characters c
LEFT JOIN group_member gm ON c.guid = gm.memberGuid
WHERE c.account = p_account_id
AND c.online = 1;
END$$
-- =====================================================
-- MONITORING PROCEDURES
-- =====================================================
-- Log query performance
CREATE PROCEDURE `sp_log_query_performance`(
IN p_operation VARCHAR(50),
IN p_start_time DATETIME(6),
IN p_bot_count INT
)
BEGIN
DECLARE v_duration_ms INT;
SET v_duration_ms = TIMESTAMPDIFF(MICROSECOND, p_start_time, NOW(6)) / 1000;
INSERT INTO playerbot_performance
(timestamp, operation, duration_ms, bot_count)
VALUES
(NOW(), p_operation, v_duration_ms, p_bot_count);
END$$
DELIMITER ;
-- =====================================================
-- OPTIMIZE EXISTING QUERIES WITH HINTS
-- =====================================================
-- Example: Force index usage for better performance
-- Original slow query
-- SELECT * FROM characters WHERE account = ? AND online = 0;
-- Optimized with index hint and column selection
-- SELECT guid, name, level, class FROM characters USE INDEX (idx_bot_login_covering)
-- WHERE account = ? AND online = 0;
-- =====================================================
-- QUERY REWRITING EXAMPLES
-- =====================================================
-- BEFORE: N+1 Query Pattern
-- SELECT guid FROM characters WHERE account = 1;
-- SELECT * FROM character_stats WHERE guid = 1;
-- SELECT * FROM character_stats WHERE guid = 2;
-- ... (N queries)
-- AFTER: Single JOIN Query
-- SELECT c.guid, c.name, cs.*
-- FROM characters c
-- INNER JOIN character_stats cs ON c.guid = cs.guid
-- WHERE c.account = 1;
-- =====================================================
-- EXECUTION PLAN VERIFICATION
-- =====================================================
-- Verify covering index usage
EXPLAIN FORMAT=JSON
SELECT guid, name, race, class, level, zone, map, position_x, position_y, position_z
FROM characters USE INDEX (idx_bot_login_covering)
WHERE account = 1 AND online = 0;
-- Verify batch operation efficiency
EXPLAIN FORMAT=JSON
SELECT c.*, cs.*
FROM characters c
INNER JOIN character_stats cs ON c.guid = cs.guid
WHERE c.guid IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
-- =====================================================
-- PERFORMANCE METRICS
-- =====================================================
-- Expected query improvements:
-- - Single bot login: 287ms -> <50ms
-- - Batch load 100 bots: 18s -> <2s
-- - Position updates: 5ms -> <1ms per bot
-- - Nearby bot search: 150ms -> <10ms
-- - Bulk state save (100 bots): 500ms -> <100ms
-- =====================================================