-- ===================================================== -- 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 -- =====================================================