by Ivan Chepurnyi
* - Applicable for M2, M1 and MySQL
So if you don't like the boring numbers, just ignore them :)
Majority of all bottlenecks in PHP applications are coming from interactions with database
So we need to give some
❤️
MySQL
MySQL is just an application
So we need to stop executing queries like this on big databases on every customer request...
SELECT `main`.`entity_id`,
IF(attribute_is_active_scope.value_id IS NOT NULL, attribute_is_active_scope.value, attribute_is_active_default.value) AS `is_active`,
`attribute_firstname_default`.`value` AS `firstname`,
`attribute_lastname_default`.`value` AS `lastname`,
/* ... ommited */
INNER JOIN `entity_int` AS `attribute_is_active_default` ON attribute_is_active_default.entity_id = main.entity_id AND attribute_is_active_default.attribute_id = '15' AND attribute_is_active_default.scope_id = 0
LEFT JOIN `entity_int` AS `attribute_is_active_scope` ON attribute_is_active_default.entity_id = attribute_is_active_scope.entity_id AND attribute_is_active_default.attribute_id = attribute_is_active_scope.attribute_id AND attribute_is_active_scope.scope_id = '1'
INNER JOIN `entity_varchar` AS `attribute_firstname_default` ON attribute_firstname_default.entity_id = main.entity_id AND attribute_firstname_default.attribute_id = '1' AND attribute_firstname_default.scope_id = 0
INNER JOIN `entity_varchar` AS `attribute_lastname_default` ON attribute_lastname_default.entity_id = main.entity_id AND attribute_lastname_default.attribute_id = '2' AND attribute_lastname_default.scope_id = 0
INNER JOIN `entity_varchar` AS `attribute_email_default` ON attribute_email_default.entity_id = main.entity_id AND attribute_email_default.attribute_id = '5' AND attribute_email_default.scope_id = 0
INNER JOIN `entity_varchar` AS `attribute_country_default` ON attribute_country_default.entity_id = main.entity_id AND attribute_country_default.attribute_id = '9' AND attribute_country_default.scope_id = 0
LEFT JOIN `entity_varchar` AS `attribute_country_scope` ON attribute_country_default.entity_id = attribute_country_scope.entity_id AND attribute_country_default.attribute_id = attribute_country_scope.attribute_id AND attribute_country_scope.scope_id = '1'
INNER JOIN `entity_datetime` AS `attribute_dob_default` ON attribute_dob_default.entity_id = main.entity_id AND attribute_dob_default.attribute_id = '7' AND attribute_dob_default.scope_id = 0 WHERE (IF(attribute_is_active_scope.value_id IS NOT NULL, attribute_is_active_scope.value, attribute_is_active_default.value) = 1)
This queries can eat all the RAM and bring your database server down
MySQL performance tuning should start from optimizing slow queries
Increasing RAM and buffer pool does not solve a problem
Optimization starts from simplifying your queries
Select identifiers
SELECT entity_id FROM entity_flat
WHERE is_active = 1 and store_id = 1
LIMIT 0, 100
Select data
SELECT entity_id, attribute_id, scope_id, value
FROM entity_varchar
WHERE entity_id IN(1, 2, 3 ....)
UNION ALL
SELECT entity_id, attribute_id, scope_id, value
FROM entity_decimal
WHERE entity_id IN(1, 2, 3 ....)
ORDER BY scope_id
MySQL memory consuption is increasing with amount of records you need to update.
These are known MySQL serial killers:
INSERT INTO .. SELECT ... FROM
UPDATE ... JOIN ... SET
Select boundaries
SELECT MIN(entity_id), MAX(entity_id) FROM entity
Create ranged inserts
INSERT INTO `entity_flat`
(`entity_id`, `scope_id`)
SELECT `main`.`entity_id`, '4' AS `scope_id`
FROM `entity` AS `main`
WHERE (`main`.`entity_id` >= 1) AND (`main`.`entity_id` < 20001)
Flat Data's main purpose is not a retrieval
As more data you have, as slower it is to navigate to the last page
SELECT * FROM `entity_flat_data`
WHERE (is_active = 1) AND (scope_id = '5')
ORDER BY `firstname` ASC LIMIT 50 OFFSET 450121
Use a separate query for limit
Select ids at particular page
SELECT `entity_id` FROM `entity_flat`
WHERE (scope_id = '2') AND (is_active = 1)
ORDER BY `firstname` ASC LIMIT 100 OFFSET 8881
Select the data
SELECT `entity_flat_data`.* FROM `entity_flat_data`
WHERE (scope_id = '2')
AND (entity_id IN(3389, 7032, 2223, 724, 5076 ... ))
You can download benchmark base from this repository: https://github.com/EcomDev/mysql-performance-benchmark
@IvanChepurnyi
ivan@ecomdev.org