MySQL query optimization - increase speed and efficiency - mysql

I want to get some emails in a database and each email have a status. All the possible status are stock in a table where they all have permissions (such as show, edit, delete, etc.). Those emails are not users with permissions trough a site but a list of emails a user have added.
Here is the tables structure:
Email table
CREATE TABLE IF NOT EXISTS `email__email` (
`email_id` int(11) NOT NULL AUTO_INCREMENT,
`created` timestamp NULL DEFAULT NULL,
`updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`user_fk` int(11) NOT NULL,
`status_fk` tinyint(2) NOT NULL,
`language` enum('fr','en') COLLATE utf8_unicode_ci DEFAULT NULL,
`email` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`firstName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`lastName` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`companyName` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`gender` enum('f','m') COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`email_id`),
UNIQUE KEY `user_email` (`user_fk`,`email`),
KEY `user_fk` (`user_fk`),
KEY `created` (`created`),
KEY `status_fk` (`status_fk`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=3031492 ;
Status table
CREATE TABLE IF NOT EXISTS `email__status` (
`status_id` int(11) NOT NULL AUTO_INCREMENT,
`name_fr` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`name_en` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`description_fr` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
`description_en` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
`permShow` tinyint(1) NOT NULL DEFAULT '0',
`permSend` tinyint(1) NOT NULL DEFAULT '0',
`permEdit` tinyint(1) NOT NULL DEFAULT '0',
`permDelete` tinyint(1) NOT NULL DEFAULT '0',
`permImport` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`status_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=7 ;
Here is the slow query with the EXPLAIN:
SELECT EE.*, ES.name_fr AS statusName, ES.description_fr AS statusDescription, ES.permShow, ES.permSend, ES.permEdit, ES.permDelete, ES.permImport
, (SELECT GROUP_CONCAT(CONVERT(CONCAT(GC.name, '~', GC.group_id), CHAR(255)) SEPARATOR ',') FROM `group` GC INNER JOIN group_email GEC ON GEC.group_fk = GC.group_id WHERE GEC.email_fk = EE.email_id AND GC.deleted = 0) AS groups
FROM `email__email` EE
INNER JOIN email__status ES ON EE.status_fk = ES.status_id
WHERE 1 = 1
AND EE.user_fk = 54
AND ES.permShow = 1
ORDER BY EE.email_id DESC LIMIT 15
EXTRA ID KEY KEY_LEN POSSIBLE_KEYS REF ROWS SELECT_TYPE TABLE TYPE
Using temporary; Using filesort 1 user_email 4 user_email,user_fk,status_fk const 180681 PRIMARY EE ref
Using where; Using join buffer 1 [empty string] [empty string] PRIMARY [empty string] 6 PRIMARY ES ALL
Using index 2 email_fk 4 group_email,group_fk,email_fk mailing_dev.EE.email_id 1 DEPENDENT SUBQUERY GEC ref
Using where 2 PRIMARY 4 PRIMARY mailing_dev.GEC.group_fk 1 DEPENDENT SUBQUERY GC eq_ref
Here is a fast query with the EXPLAIN:
SELECT EE.*
, (SELECT GROUP_CONCAT(CONVERT(CONCAT(GC.name, '~', GC.group_id), CHAR(255)) SEPARATOR ',') FROM `group` GC INNER JOIN group_email GEC ON GEC.group_fk = GC.group_id WHERE GEC.email_fk = EE.email_id AND GC.deleted = 0) AS groups
FROM `email__email` EE
WHERE 1 = 1
AND EE.user_fk = 54
AND EXISTS(SELECT permShow FROM email__status WHERE status_id = EE.status_fk AND permShow = 1)
ORDER BY EE.email_id DESC LIMIT 15
EXTRA ID KEY KEY_LEN POSSIBLE_KEYS REF ROWS SELECT_TYPE TABLE TYPE
Using where 1 PRIMARY 4 user_email,user_fk [empty string] 270 PRIMARY EE index
Using where 3 PRIMARY 4 PRIMARY mailing_dev.EE.status_fk 1 DEPENDENT SUBQUERY email__status eq_ref
Using index 2 email_fk 4 group_email,group_fk,email_fk mailing_dev.EE.email_id 1 DEPENDENT SUBQUERY GEC ref
Using where 2 PRIMARY 4 PRIMARY mailing_dev.GEC.group_fk 1 DEPENDENT SUBQUERY GC eq_ref
There is a big difference between both queries but the second one doesn't give me two important columns that I need to fetch. I can do subqueries to fetch them like a join would do but still, I don't want to have a lot of subqueries for each... any ideas to improve this ?
Thanks

email__email.status_fk is a tinyint, but email__status.status_id is an int(11).
This probably is fouling up your INNER JOIN. Change one or the other data type and try again.

Related

Why is this SQL query extremely slow?

UPDATE: seems that the Query Cache is not activated, otherwise the performance would be much better from the second execution of the same query, since MySQL should have cached it already. I have MariaDB version: 10.2.41-
Protocol version: 10 , indeed the Query Cache is disabled by default starting with MariaDB 10.1.7.
Is it a good idea to enable it on my VPS config? Or i better upgrade it first?
I have a MySQL query performing very slowly on a CentOS VPS with 1GB RAM and 1 CPU Core.
The query takes 400 - 900 ms to execute (!!), which i want to figure out how to optimize.
It's over several tables, some of them pretty huge (Wordpress' wp_postmeta for example).
But apparently all columns involved are indexed properly, so i'm asking for advice on what to do, or if the only optimization possible is to upgrade the VPS.
Thanks in advance!
Here is the query and the EXPLAIN output
EXPLAIN SELECT c.post_id AS wc_variation_id,v.original_price_rand,v.price,c.meta_value AS color,s.meta_value AS size,bp.tier,t.discount_percent,MAX(IFNULL(tag.extra_price_tag,0)) AS extra_price_tag,MAX(IFNULL(lab.extra_price_label,0)) AS extra_price_label,
i.uri,i.branded,i.design_cost,ib.uri AS uri_back,ib.branded AS branded_back,ib.design_cost AS design_cost_back
FROM wp_postmeta c
JOIN brandly_tiers_bp_balances bp
JOIN brandly_tiers t ON t.role_name=bp.tier
JOIN wix_images i
JOIN wix_images_back ib
JOIN wix_imported_product_variations v ON v.wc_id=c.post_id
LEFT JOIN brandly_reseller_tags rt ON rt.user_id=bp.user_id
LEFT JOIN brandly_reseller_branded_packaging_sizes tag ON tag.size_label=rt.size_label
LEFT JOIN brandly_reseller_labels rl ON rl.user_id=bp.user_id
LEFT JOIN brandly_reseller_branded_packaging_sizes lab ON lab.size_label=rl.size_label
JOIN wp_postmeta s ON s.post_id=c.post_id
WHERE c.post_id IN (SELECT DISTINCT(wc_id) FROM wix_imported_product_variations WHERE wc_product_id = 181189)
AND s.meta_key = 'attribute_pa_size'
AND c.meta_key = 'attribute_pa_colour'
AND i.wix_variation_id=v.wix_id
AND ib.wix_variation_id=v.wix_id
AND v.store_id=36
AND bp.user_id=11
GROUP BY c.post_id
ORDER BY color,size
Output:
id
select_type
table
type
possible_keys
key
key_len
ref
rows
Extra
1
PRIMARY
bp
const
PRIMARY,tier
PRIMARY
8
const
1
Using temporary; Using filesort
1
PRIMARY
t
const
PRIMARY
PRIMARY
52
const
1
1
PRIMARY
wix_imported_product_variations
ref
wc_product_id,wc_id
wc_product_id
8
const
3
Start temporary
1
PRIMARY
rt
ref
PRIMARY
PRIMARY
8
const
5
Using index
1
PRIMARY
tag
eq_ref
PRIMARY
PRIMARY
32
brandly_xvfkg.rt.size_label
1
Using where
1
PRIMARY
c
ref
post_id,meta_key
post_id
8
brandly_xvfkg.wix_imported_product_variations.wc_id
14
Using index condition; Using where; End temporary
1
PRIMARY
v
ref
PRIMARY,store_id,wc_id
wc_id
8
brandly_xvfkg.c.post_id
1
Using index condition; Using where
1
PRIMARY
ib
ref
wix_variation_id
wix_variation_id
103
brandly_xvfkg.v.wix_id
3
1
PRIMARY
i
ref
wix_variation_id
wix_variation_id
103
brandly_xvfkg.v.wix_id
3
1
PRIMARY
s
ref
post_id,meta_key
post_id
8
brandly_xvfkg.c.post_id
14
Using where
1
PRIMARY
rl
ref
PRIMARY
PRIMARY
8
const
5
Using index
1
PRIMARY
lab
eq_ref
PRIMARY
PRIMARY
32
brandly_xvfkg.rl.size_label
1
Using where
EDIT: The subquery takes 0.0005 seconds , so it's not the culprit
EDIT2: the wp_postmeta table has 1709642 records and it's MyISAM:
SHOW CREATE TABLE `wp_postmeta`;
CREATE TABLE `wp_postmeta` (
`meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`post_id` bigint(20) unsigned NOT NULL DEFAULT 0,
`meta_key` varchar(255) DEFAULT NULL,
`meta_value` longtext DEFAULT NULL,
PRIMARY KEY (`meta_id`),
KEY `post_id` (`post_id`),
KEY `meta_key` (`meta_key`(191))
) ENGINE=MyISAM AUTO_INCREMENT=3945402 DEFAULT CHARSET=utf8
EDIT3: actually all tables are MyISAM except wix_images, wix_images_back, wix_imported_product_variations which are innoDB
EDIT4: just converted all tables to innoDB. The performance is better now, it takes 200-400 ms. Still slow though.
EDIT5: EXPLAIN output changed a bit now, after switching the tables to innoDB. See https://codebeautify.org/htmlviewer/y225302bb
EDIT6: here are the definitions of the tables involved
CREATE TABLE `wp_postmeta` (
`meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`post_id` bigint(20) unsigned NOT NULL DEFAULT 0,
`meta_key` varchar(255) DEFAULT NULL,
`meta_value` longtext DEFAULT NULL,
PRIMARY KEY (`meta_id`),
KEY `post_id` (`post_id`),
KEY `meta_key` (`meta_key`(191))
) ENGINE=InnoDB AUTO_INCREMENT=3945402 DEFAULT CHARSET=utf8
CREATE TABLE `brandly_tiers_bp_balances` (
`user_id` bigint(20) NOT NULL,
`bp_balance` bigint(20) NOT NULL DEFAULT 0,
`bp_pending_balance` bigint(20) NOT NULL DEFAULT 0,
`tier` varchar(50) COLLATE latin1_general_ci NOT NULL DEFAULT 'Earth',
`tier_change_ts` bigint(20) DEFAULT NULL,
PRIMARY KEY (`user_id`),
KEY `tier` (`tier`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `brandly_tiers` (
`role_name` varchar(50) COLLATE latin1_general_ci NOT NULL,
`bp_threshold` int(11) NOT NULL DEFAULT 0,
`discount_percent` smallint(6) NOT NULL,
`bp_percent_branded_orders` smallint(6) NOT NULL,
`bp_percent_bulk_orders` smallint(6) NOT NULL,
`bp_percent_courier` smallint(6) NOT NULL,
`bp_percent_bulk_branded_packaging` smallint(6) NOT NULL,
`bp_percent_monthly_subscription_fee` smallint(6) NOT NULL,
`bulk_order_minimum_quantity` int(11) NOT NULL DEFAULT 10,
`bp_bulk_order_cap` int(11) NOT NULL DEFAULT 7500,
`tier_monthly_subscription_price` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`role_name`),
UNIQUE KEY `bp_threshold` (`bp_threshold`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `wix_images` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`filename` text COLLATE latin1_general_ci NOT NULL,
`uri` longtext COLLATE latin1_general_ci NOT NULL,
`background_image_uri` longtext COLLATE latin1_general_ci NOT NULL,
`branded` tinyint(1) DEFAULT 0,
`wix_id` varchar(100) COLLATE latin1_general_ci DEFAULT NULL,
`wix_product_id` varchar(100) COLLATE latin1_general_ci DEFAULT NULL,
`wix_variation_id` varchar(100) COLLATE latin1_general_ci DEFAULT NULL,
`design_cost` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `wix_id` (`wix_id`),
KEY `wix_product_id` (`wix_product_id`),
KEY `wix_variation_id` (`wix_variation_id`),
KEY `branded` (`branded`),
KEY `background_image_uri` (`background_image_uri`(8))
) ENGINE=InnoDB AUTO_INCREMENT=10680 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `wix_images_back` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`filename` text COLLATE latin1_general_ci NOT NULL,
`uri` longtext COLLATE latin1_general_ci NOT NULL,
`background_image_uri` longtext COLLATE latin1_general_ci NOT NULL,
`branded` tinyint(1) DEFAULT 0,
`wix_id` varchar(100) COLLATE latin1_general_ci DEFAULT NULL,
`wix_product_id` varchar(100) COLLATE latin1_general_ci DEFAULT NULL,
`wix_variation_id` varchar(100) COLLATE latin1_general_ci DEFAULT NULL,
`design_cost` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `wix_id` (`wix_id`),
KEY `wix_product_id` (`wix_product_id`),
KEY `wix_variation_id` (`wix_variation_id`),
KEY `branded` (`branded`),
KEY `background_image_uri` (`background_image_uri`(8))
) ENGINE=InnoDB AUTO_INCREMENT=7733 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `wix_imported_product_variations` (
`wix_id` varchar(100) COLLATE latin1_general_ci NOT NULL,
`store_id` int(11) NOT NULL,
`wc_id` bigint(20) NOT NULL,
`wc_product_id` bigint(20) NOT NULL,
`sku` varchar(100) COLLATE latin1_general_ci NOT NULL,
`price` decimal(18,2) NOT NULL,
`original_price_rand` decimal(18,2) NOT NULL,
`has_markup` tinyint(1) DEFAULT 1,
`wix_image_id` varchar(1000) COLLATE latin1_general_ci NOT NULL,
`imported_at` datetime DEFAULT NULL,
`tmp_design_image` text COLLATE latin1_general_ci DEFAULT NULL,
`tmp_branded` tinyint(1) DEFAULT NULL,
`tmp_design_cost` decimal(18,2) DEFAULT NULL,
`tmp_design_image_back` text COLLATE latin1_general_ci DEFAULT NULL,
`tmp_branded_back` tinyint(1) DEFAULT NULL,
`tmp_design_cost_back` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`wix_id`),
KEY `sku` (`sku`),
KEY `store_id` (`store_id`),
KEY `has_markup` (`has_markup`),
KEY `imported_at` (`imported_at`),
KEY `wc_product_id` (`wc_product_id`),
KEY `wc_id` (`wc_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `brandly_reseller_tags` (
`user_id` bigint(20) NOT NULL,
`size_label` varchar(30) COLLATE latin1_general_ci NOT NULL,
`image_front1` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_back1` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_front2` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_back2` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_front3` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_back3` mediumtext COLLATE latin1_general_ci NOT NULL,
`default_index` int(11) DEFAULT NULL,
`design_cost` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`user_id`,`size_label`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `brandly_reseller_labels` (
`user_id` bigint(20) NOT NULL,
`size_label` varchar(30) COLLATE latin1_general_ci NOT NULL,
`image_front1` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_back1` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_front2` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_back2` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_front3` mediumtext COLLATE latin1_general_ci NOT NULL,
`image_back3` mediumtext COLLATE latin1_general_ci NOT NULL,
`default_index` int(11) DEFAULT NULL,
`design_cost` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`user_id`,`size_label`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
CREATE TABLE `brandly_reseller_branded_packaging_sizes` (
`size_label` varchar(30) COLLATE latin1_general_ci NOT NULL,
`size1_cm` int(11) NOT NULL,
`size2_cm` int(11) NOT NULL,
`size3_cm` int(11) NOT NULL,
`is_bag` tinyint(1) DEFAULT NULL,
`max_weight_kg` decimal(18,2) NOT NULL,
`extra_price` decimal(18,2) NOT NULL,
`extra_price_gift` decimal(18,2) NOT NULL,
`extra_price_tag` decimal(18,2) NOT NULL,
`extra_price_label` decimal(18,2) NOT NULL,
PRIMARY KEY (`size_label`),
KEY `size1_cm` (`size1_cm`),
KEY `size2_cm` (`size2_cm`),
KEY `size3_cm` (`size3_cm`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
EDIT7:
moved the ON conditions from the JOINs to the WHERE. Execution time 200-400ms like before.
EXPLAIN SELECT c.post_id AS wc_variation_id,v.original_price_rand,v.price,c.meta_value AS color,s.meta_value AS size,bp.tier,t.discount_percent,MAX(IFNULL(tag.extra_price_tag,0)) AS extra_price_tag,MAX(IFNULL(lab.extra_price_label,0)) AS extra_price_label,
i.uri,i.branded,i.design_cost,ib.uri AS uri_back,ib.branded AS branded_back,ib.design_cost AS design_cost_back
FROM wp_postmeta c
JOIN brandly_tiers_bp_balances bp
JOIN brandly_tiers t
JOIN wix_images i
JOIN wix_images_back ib
JOIN wix_imported_product_variations v
LEFT JOIN brandly_reseller_tags rt ON rt.user_id=bp.user_id
LEFT JOIN brandly_reseller_branded_packaging_sizes tag ON tag.size_label=rt.size_label
LEFT JOIN brandly_reseller_labels rl ON rl.user_id=bp.user_id
LEFT JOIN brandly_reseller_branded_packaging_sizes lab ON lab.size_label=rl.size_label
JOIN wp_postmeta s
WHERE c.post_id IN (SELECT DISTINCT(wc_id) FROM wix_imported_product_variations WHERE wc_product_id = 181189)
AND s.meta_key = 'attribute_pa_size'
AND c.meta_key = 'attribute_pa_colour'
AND i.wix_variation_id=v.wix_id
AND ib.wix_variation_id=v.wix_id
AND v.store_id=36
AND bp.user_id=11
AND s.post_id=c.post_id
AND t.role_name=bp.tier
AND v.wc_id=c.post_id
GROUP BY c.post_id
ORDER BY color,size
EXPLAIN output
id
select_type
table
type
possible_keys
key
key_len
ref
rows
Extra
1
PRIMARY
bp
const
PRIMARY,tier
PRIMARY
8
const
1
Using temporary; Using filesort
1
PRIMARY
t
const
PRIMARY
PRIMARY
52
const
1
1
PRIMARY
wix_imported_product_variations
ref
wc_product_id,wc_id
wc_product_id
8
const
3
Start temporary
1
PRIMARY
rt
ref
PRIMARY
PRIMARY
8
const
5
Using index
1
PRIMARY
tag
eq_ref
PRIMARY
PRIMARY
32
brandly_xvfkg.rt.size_label
1
Using where
1
PRIMARY
c
ref
post_id,meta_key
post_id
8
brandly_xvfkg.wix_imported_product_variations.wc_id
8
Using index condition; Using where; End temporary
1
PRIMARY
v
ref
PRIMARY,store_id,wc_id
wc_id
8
brandly_xvfkg.c.post_id
1
Using index condition; Using where
1
PRIMARY
ib
ref
wix_variation_id
wix_variation_id
103
brandly_xvfkg.v.wix_id
3
1
PRIMARY
i
ref
wix_variation_id
wix_variation_id
103
brandly_xvfkg.v.wix_id
3
1
PRIMARY
s
ref
post_id,meta_key
post_id
8
brandly_xvfkg.v.wc_id
8
Using index condition; Using where
1
PRIMARY
rl
ref
PRIMARY
PRIMARY
8
const
5
Using index
1
PRIMARY
lab
eq_ref
PRIMARY
PRIMARY
32
brandly_xvfkg.rl.size_label
1
Using where
Spending more money on a bigger server is unlikely to do you much good.
200-400ms for a query like this is not bad. There's probably no SQL magic that will get it to under 100ms; it's doing a lot. Often the words extremely slow refer to queries that take minutes or longer.
MAX(IFNULL(column, 0)) yields precisely the same result as MAX(column) as long as all values of column are positive.. Aggregate functions skip over nulls. You might be able to simplify the two places you use that expression.
Your query is hard to reason about. If this were my query I would rewrite it to get rid of the short table aliases, and rather spell them out. And, I would rewrite it so every JOIN (both LEFT and ordinary) had an ON-clause with it. Notice that ON-clauses can be compound:
FROM a
JOIN wp_postmeta ON a.id = wp_postmeta.postid
AND wp_postmeta.meta_key = 'attribute_pa_size'
That's a query pattern we WordPress hackers instantly recognize.
For ordinary JOINs, moving ON-clauses to WHERE-clauses has no effect on execution plans.
Your join types, in your plan, are all "const", "eq-ref", or "ref". That is very good.
There's a field-proven rework of WordPress's postmeta table indexes. Your postmeta table is big enough -- almost four megarows -- that you probably can use it. It changes the primary key to a compound key that meets the requirement of your query pattern. Yours is a common pattern in WooCommerce installations. It is this for MySQL 5.7 and above.
ALTER TABLE wp_postmeta
ADD UNIQUE KEY meta_id (meta_id),
DROP PRIMARY KEY,
ADD PRIMARY KEY (post_id, meta_key, meta_id),
DROP KEY meta_key,
ADD KEY meta_key (meta_key, meta_value(32), post_id, meta_id),
ADD KEY meta_value (meta_value(32), meta_id),
DROP KEY post_id;
The Index WP MySQL For Speed plugin is designed to install this set of indexes for you, and do its best with MySQL / MariaDB 5.5 and up.
I don't see any big problems with indexing on your non-WordPress tables. There may be some opportunities for compound covering indexes, but, again, your query is hard to read.
95% of systems are better off with the Query Cache turned off. Remember that any modification to a table causes the QC to purge all entries for that table. If there are a lot of writes to a table, that could be a lot of CPU cycles for purging.
If you have only 1GB of RAM, there is not much room for the QC; use the room for InnoDB's buffer_pool instead. When you changed from MyISAM did you lower key_buffer_size and raise innodb_buffer_pool_size?
Some of these composite indexes may help:
c: INDEX(post_id, meta_key, meta_value)
v: INDEX(wix_id, store_id, wc_id, original_price_rand, price)
s: INDEX(meta_key, post_id, meta_value)
bp: INDEX(user_id, tier)
t: INDEX(role_name, discount_percent)
tag: INDEX(size_label, extra_price_tag)
lab: INDEX(size_label, extra_price_label)
i: INDEX(wix_variation_id, uri, branded, design_cost)
ib: INDEX(wix_variation_id, uri, branded, design_cost)
rt: INDEX(user_id, size_label)
rl: INDEX(user_id, size_label)
When adding a composite index, DROP index(es) with the same leading columns.
That is, when you have both INDEX(a) and INDEX(a,b), toss the former.

MySQL - Any addtional indexes would speed up this query?

I see that my query does full table scan and takes a lot of time. I heard that making indexes would speed this up and I have added some to the tables. Is there any other indexes I should create to make this query faster?
My query is:
SELECT p.id, n.people_type_id, n.full_name, n.post, p.nick,
p.key_name, p.email, p.internal_user_id FROM email_routing e
JOIN people_emails p ON p.id=e.receiver_email_id
JOIN people n ON n.id = p.people_id
WHERE e.message_id = 897360 AND e.basket=1
Here is the EXPLAIN result:
EXPLAIN SELECT p.id, n.people_type_id, n.full_name, n.post, p.nick,
p.key_name, p.email, p.internal_user_id FROM email_routing e
JOIN people_emails p ON p.id=e.receiver_email_id
JOIN people n ON n.id = p.people_id
WHERE e.message_id = 897360 AND e.basket=1
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
1 SIMPLE n NULL ALL PRIMARY NULL NULL NULL 1 100.00 NULL
1 SIMPLE p NULL ALL PRIMARY NULL NULL NULL 3178 10.00 Using where; Using join buffer (Block Nested Loop)
1 SIMPLE e NULL ref bk1 bk1 4 server.p.id 440 1.00 Using where; Using
And here are the tables strucutre:
SHOW CREATE TABLE people_emails;
CREATE TABLE `people_emails` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nick` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`key_name` varchar(255) NOT NULL,
`people_id` int(11) NOT NULL,
`status` int(11) NOT NULL DEFAULT '0',
`activity` int(11) NOT NULL,
`internal_user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
FULLTEXT KEY `email` (`email`)
) ENGINE=MyISAM AUTO_INCREMENT=22114 DEFAULT CHARSET=utf8
SHOW CREATE TABLE email_routing;
CREATE TABLE `email_routing` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`message_id` int(11) NOT NULL,
`sender_email_id` int(11) NOT NULL,
`receiver_email_id` int(11) NOT NULL,
`basket` int(11) NOT NULL,
`status` int(11) NOT NULL,
`popup` int(11) NOT NULL DEFAULT '0',
`tm` int(11) NOT NULL DEFAULT '0',
KEY `id` (`id`),
KEY `bk1` (`receiver_email_id`,`status`,`sender_email_id`,`message_id`,`basket`),
KEY `bk2` (`sender_email_id`,`tm`)
) ENGINE=InnoDB AUTO_INCREMENT=1054618 DEFAULT CHARSET=utf8
SHOW CREATE TABLE people;
CREATE TABLE `people` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`fname` varchar(255) CHARACTER SET cp1251 NOT NULL,
`lname` varchar(255) CHARACTER SET cp1251 NOT NULL,
`patronymic` varchar(255) CHARACTER SET cp1251 NOT NULL,
`gender` tinyint(1) NOT NULL,
`full_name` varchar(255) NOT NULL DEFAULT ' ',
`category` int(11) NOT NULL,
`people_type_id` int(255) DEFAULT NULL,
`tags` varchar(255) CHARACTER SET cp1251 NOT NULL,
`job` varchar(255) CHARACTER SET cp1251 NOT NULL,
`post` varchar(255) CHARACTER SET cp1251 NOT NULL,
`profession` varchar(255) CHARACTER SET cp1251 DEFAULT NULL,
`zip` varchar(16) CHARACTER SET cp1251 NOT NULL,
`country` int(11) DEFAULT NULL,
`region` varchar(10) NOT NULL,
`city` varchar(255) CHARACTER SET cp1251 NOT NULL,
`address` varchar(255) CHARACTER SET cp1251 NOT NULL,
`address_date` date DEFAULT NULL,
`inner` tinyint(4) NOT NULL,
`contact_through` varchar(255) DEFAULT '',
`next_call` date NOT NULL,
`additional` text CHARACTER SET cp1251 NOT NULL,
`user_id` int(11) NOT NULL,
`changed` datetime NOT NULL,
`status` int(11) DEFAULT NULL,
`nick` varchar(255) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`last_update_ts` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`area` text NOT NULL,
`reviewed_` tinyint(4) NOT NULL,
`phones_old` text NOT NULL,
`post_sticker` text NOT NULL,
`permissions` int(120) NOT NULL DEFAULT '0',
`internal_user_id` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `most_used` (`category`,`status`,`city`,`lname`,`next_call`),
KEY `registrars` (`category`,`status`,`contact_through`,`next_call`),
FULLTEXT KEY `lname` (`lname`),
FULLTEXT KEY `fname` (`fname`),
FULLTEXT KEY `mname` (`patronymic`),
FULLTEXT KEY `Full Name` (`full_name`)
) ENGINE=MyISAM AUTO_INCREMENT=415009 DEFAULT CHARSET=utf8
How to choose columns for building indexes, should I pick text columnts too or that only will work with numer columnts
The table email_routing seem to have 1054618 rows .
And you try to find one row , by message_id .
e.message_id = 897360
BUT message_id must be indexed to speed-up the query .
message_id is part of the index bk1 , but this is not enough because message_id is not the first columns of the index .
email_routing needs
INDEX ( message_id, basket, -- first, in either order
receiver_email_id ) -- for "covering"
Your bk1 starts with receiver_email_id; this is not nearly as good.
Include column(s) in WHERE that are tested with =.
Include other columns from WHERE, GROUP BY, and ORDER BY (none in your case); the order is important, but beyond the scope of this discussion.
Include any other columns of the same table used anywhere in the query -- this is to make it a "covering" index. But don't bother if this would lead to more than, say, 5 columns or would involve TEXT, which cannot be in an index.
Then move on to the other tables. In both JOINs, it seems that they would be hit by their PRIMARY KEYs (JOIN x ON x.id = ...)
More discussion: Cookbook for creating indexes
On other issues...
You really should move to InnoDB. As of 5.6, it includes FULLTEXT, but there are some differences. In particular, you may need more fulltext indexes. For example, MATCH(lname, fname) requires FULLTEXT(lname, fname).
Do you really want to stick to cp1251? It limits your internalization mostly to English, Russian, Bulgarian, Serbian and Macedonian. And it is unclear how well FULLTEXT (MyISAM or InnoDB) will work with those non-English languages.
INTs are always 4 bytes; consider using smaller versions.
Is there really only one people? The Optimizer decided that was the best table to start with, but it wasn't. I'm hoping my improved index on email_routing will trick it into starting with that table -- which will definitely be optimal.

Running MySQL on RPI / optimising query

I have a raspberry PI 3 running MySQL 5.5.57 - this is the only service running on the RPI.
My app makes a key query (below) which takes 5-7 sec to execute on the MySQL server.
I have done a lot to optimize indexes and FK but it really hasn't helped much. When I do an explain I see that it is using temporary and filesort, which I don't really understand.
Are there any configuration tweaks which should be done when running mysql on a RPI. I don't know much about the various buffers...
Is there anything else I should do to optimise the query?
The table has about 30.000 rows and growing...
This is the query:
SELECT SQL_NO_CACHE distinct `photos`.*
FROM `photos`
LEFT OUTER JOIN `facets` ON `photos`.`id` = `facets`.`photo_id`
WHERE (`photos`.`date_taken` <= '2017-08-24')
AND (photos.status != 1 or photos.status is NULL)
ORDER BY photos.date_taken DESC LIMIT 500 OFFSET 500;
This is the table setup:
CREATE TABLE `photos` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`date_taken` datetime DEFAULT NULL,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`file_extension` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`file_size` int(11) DEFAULT NULL,
`location_id` bigint(20) DEFAULT NULL,
`make` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`model` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`original_height` int(11) DEFAULT NULL,
`original_width` int(11) DEFAULT NULL,
`longitude` decimal(16,10) DEFAULT NULL,
`latitude` decimal(16,10) DEFAULT NULL,
`status` int(11) DEFAULT ''0'',
`phash` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
`org_id` int(11) DEFAULT NULL,
`lg_id` int(11) DEFAULT NULL,
`md_id` int(11) DEFAULT NULL,
`tm_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `index_photos_on_location_id` (`location_id`),
KEY `index_photos_on_date_taken` (`date_taken`),
KEY `index_photos_on_status` (`status`),
KEY `index_photos_on_phash` (`phash`),
CONSTRAINT `fk_rails_47f4e5f105` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=25672 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
When I do an explain on the query then this is what I get:
+----+-------------+--------+-------+---------------------------------------------------+----------------------------+---------+-------------------+-------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------+-------+---------------------------------------------------+----------------------------+---------+-------------------+-------+----------------------------------------------+
| 1 | SIMPLE | photos | range | index_photos_on_date_taken,index_photos_on_status | index_photos_on_date_taken | 9 | NULL | 13147 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | facets | ref | index_facets_on_photo_id | index_facets_on_photo_id | 9 | pt_prod.photos.id | 1 | Using index; Distinct |
+----+-------------+--------+-------+---------------------------------------------------+----------------------------+---------+-------------------+-------+----------------------------------------------+
The query will at times be extended to:
SELECT DISTINCT `photos`.*
FROM `photos`
LEFT OUTER JOIN `facets` ON `photos`.`id` = `facets`.`photo_id`
LEFT OUTER JOIN `tags` ON `facets`.`source_id` = `tags`.`id`
LEFT OUTER JOIN `comments` ON `facets`.`source_id` = `comments`.`id`
WHERE `photos`.`date_taken` >= '2017-01-25'
AND `photos`.`date_taken` <= '2018-01-10'
AND `locations`.`country_id` = 16
AND `locations`.`city_id` = 21
OR `facets`.`source_id` = 9 AND `facets`.`type` = 'AlbumFacet'
OR `facets`.`source_id` = 9 AND `facets`.`type` = 'TagFacet'
THIS ANSWERS THE ORIGINAL VERSION OF THE QUESTION.
Your query is only using columns form the first table. I would write it as:
SELECT SQL_NO_CACHE `photos`.*
FROM `photos` p
LEFT OUTER JOIN `facets` ON `photos`.`id` = `facets`.`photo_id`
WHERE (p.`date_taken` <= '2017-08-24') AND (NOT p.status <=> 1) AND
EXISTS (SELECT 1 FROM facets f WHERE pid = f.photo_id)
ORDER BY p.date_taken DESC
LIMIT 500 OFFSET 500;
Removing the SELECT DISTINCT should be a bit win. You should also have an index on facets(photo_id).
An index on (date_taken, status) might help. However, it is not clear how selective your conditions are, so an index on photos might not be of much use.

Avoid filesort with INNER JOIN + ORDER BY

I've been reading other posts but I didn't managed to fix my query.
Using DESC order the query is x20 times slower, I must improve that.
This is the query:
SELECT posts.post_id, posts.post_b_id, posts.post_title, posts.post_cont, posts.thumb, posts.post_user, boards.board_title_l, boards.board_title
FROM posts
INNER JOIN follow ON posts.post_b_id = follow.board_id
INNER JOIN boards ON posts.post_b_id = boards.board_id
WHERE follow.user_id =1
ORDER BY posts.post_id DESC
LIMIT 10
And these are the tables (Updated):
CREATE TABLE IF NOT EXISTS `posts` (
`post_id` int(11) NOT NULL AUTO_INCREMENT,
`post_b_id` int(11) unsigned NOT NULL,
`post_title` varchar(50) COLLATE utf8_bin NOT NULL,
`post_cont` text COLLATE utf8_bin NOT NULL,
`post_mintxt` varchar(255) COLLATE utf8_bin NOT NULL,
`post_type` char(3) COLLATE utf8_bin NOT NULL,
`thumb` varchar(200) COLLATE utf8_bin NOT NULL,
`post_user` varchar(16) COLLATE utf8_bin NOT NULL,
`published` enum('0','1') COLLATE utf8_bin NOT NULL,
`post_ip` varchar(94) COLLATE utf8_bin NOT NULL,
`post_ip_dat` int(11) unsigned NOT NULL,
`post_up` int(10) unsigned NOT NULL DEFAULT '0',
`post_down` int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`post_id`),
KEY `post_b_id` (`post_b_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=405 ;
CREATE TABLE IF NOT EXISTS `boards` (
`board_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`board_title_l` varchar(19) COLLATE utf8_bin NOT NULL,
`board_user_id` int(10) unsigned NOT NULL,
`board_title` varchar(19) COLLATE utf8_bin NOT NULL,
`board_user` varchar(16) COLLATE utf8_bin NOT NULL,
`board_txt` tinyint(1) unsigned NOT NULL,
`board_img` tinyint(1) unsigned NOT NULL,
`board_vid` tinyint(1) unsigned NOT NULL,
`board_desc` varchar(100) COLLATE utf8_bin NOT NULL,
`board_mod_p` tinyint(3) unsigned NOT NULL DEFAULT '0',
`board_ip` varchar(94) COLLATE utf8_bin NOT NULL,
`board_dat_ip` int(11) unsigned NOT NULL,
PRIMARY KEY (`board_id`),
UNIQUE KEY `board_title_l` (`board_title_l`),
KEY `board_user_id` (`board_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=89 ;
CREATE TABLE IF NOT EXISTS `follow` (
`user_id` int(10) unsigned NOT NULL,
`board_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`user_id`,`board_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
Using default ASC order it only uses index and where, with DESC uses index, where, temporary and filesort.
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE follow ref user_id user_id 4 const 2 100.00 Using index; Using temporary; Using filesort
1 SIMPLE boards eq_ref PRIMARY PRIMARY 4 xxxx.follow.board_id 1 100.00
1 SIMPLE posts ref post_b_id post_b_id 4 xxxx.boards.board_id 3 100.00 Using where
How I can make the query receiving the results in DESC order without filesort and temporary.
UPDATE: I made a new query, no temporary or filesort, but type: index, filtered: 7340.00. Almost as fast as ASC order if the posts are at the end of the table, but slow if the posts that is searching are at the beginning. So seems better but it's not enough.
SELECT posts.post_id, posts.post_b_id, posts.post_title, posts.post_cont, posts.thumb, posts.post_user, boards.board_title_l, boards.board_title
FROM posts INNER JOIN boards ON posts.post_b_id = boards.board_id
WHERE posts.post_b_id
IN (
SELECT follow.board_id
FROM follow
WHERE follow.user_id = 1
)
ORDER BY posts.post_id DESC
LIMIT 10
Explain:
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY posts index post_b_id PRIMARY 8 NULL 10 7340.00 Using where
1 PRIMARY boards eq_ref PRIMARY PRIMARY 4 xxxx.posts.post_b_id 1 100.00
2 DEPENDENT SUBQUERY follow eq_ref user_id user_id 8 const,func 1 100.00 Using index
UPDATE: Explain for the query from dened's answer:
id select_type table type possible_keys key key_len ref rows filtered Extra
1 PRIMARY <derived2>ALL NULL NULL NULL NULL 10 100.00
1 PRIMARY posts eq_ref PRIMARY,post_b_id PRIMARY 4 sq.post_id 1 100.00
1 PRIMARY boards eq_ref PRIMARY PRIMARY 4 xxxx.posts.post_b_id 1 100.00
2 DERIVED follow ref PRIMARY PRIMARY 4 1 100.00 Using index; Using temporary; Using filesort
2 DERIVED posts ref post_b_id post_b_id 4 xxxx.follow.board_id 6 100.00 Using index
Times:
Original query no order (ASC): 0.187500 seconds
Original query DESC: 2.812500 seconds
Second query posts at the end (DESC): 0.218750 seconds
Second query posts at the beginning (DESC): 3.293750 seconds
dened's query DESC: 0.421875 seconds
dened's query no order (ASC): 0.323750 seconds
Interesting note, if I add ORDER BY ASC is as slow as DESC.
Alter the table order will be a god way, but as I said in the comments I wasn't able to do that.
You can help MySQL optimizer by moving all the filtering work to a subquery that accesses only indices (manipulating indices is usually much faster than manipulating other data), and fetching rest of the data in the outermost query:
SELECT posts.post_id,
posts.post_b_id,
posts.post_title,
posts.post_cont,
posts.thumb,
posts.post_user,
boards.board_title_l,
boards.board_title
FROM (SELECT post_id
FROM posts
JOIN follow
ON posts.post_b_id = follow.board_id
WHERE follow.user_id = 1
ORDER BY post_id DESC
LIMIT 10) sq
JOIN posts
ON posts.post_id = sq.post_id
JOIN boards
ON boards.board_id = posts.post_b_id
Note that I omit ORDER BY posts.post_id DESC from the outer query, because it is usually faster to sort the final result in your code rather than sorting using a MySQL query (MySQL often uses filesort for that).
P.S. You can replace the unique key in the follow table with a primary key.
Increasing the sort_buffer_size parameter will increase the amount of memory MySQL uses before resorting to a temporary disk file and should help considerably.

Is this the best performance using mysql count on multiple joins

I have a DB with the following tables:
employee
company
lt_employee_title
Employees can have multiple titles and companies can have multiple employees.
I'm trying to search on the lt_employee_title table and the company table and get employee_id from the employee table.
The table structures:
CREATE TABLE `employee` (
`employee_id` int(11) NOT NULL AUTO_INCREMENT,
`company_id` int(11) DEFAULT NULL,
PRIMARY KEY (`employee_id`),
KEY `employee_id` (`company_id`)
) ENGINE=InnoDB;
CREATE TABLE `company` (
`company_id` int(11) NOT NULL AUTO_INCREMENT,
`URL` varchar(100) DEFAULT NULL,
`company_name` varchar(100) DEFAULT NULL,
`date_created` date DEFAULT NULL,
`date_updated` datetime DEFAULT NULL,
`address1` varchar(45) DEFAULT NULL,
`address2` varchar(45) DEFAULT NULL,
`phone` varchar(20) DEFAULT NULL,
`phone2` varchar(20) DEFAULT NULL,
`fax` varchar(20) DEFAULT NULL,
`city` varchar(45) DEFAULT NULL,
`major_city_id` int(11) DEFAULT NULL,
`region_code` varchar(10) DEFAULT NULL,
`zip` varchar(15) DEFAULT NULL,
`country_code` varchar(2) DEFAULT NULL,
`archived` tinyint(4) NOT NULL DEFAULT '0',
`enabled` enum('yes','no') DEFAULT 'yes',
PRIMARY KEY (`company_id`),
KEY `country_code` (`country_code`),
KEY `employee` (`number_of_employees_id`),
KEY `phone` (`phone`(3)),
KEY `state` (`region_code`),
KEY `archived` (`archived`)
) ENGINE=InnoDB;
CREATE TABLE `lt_employee_title` (
`employee_id` int(11) NOT NULL,
`title_id` int(11) NOT NULL,
UNIQUE KEY `unique_field` (`employee_id`,`title_id`),
KEY `employee_id` (`employee_id`),
KEY `title_id` (`title_id`),
KEY `both` (`title_id`,`employee_id`),
KEY `both2` (`employee_id`,`title_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8$$;
This query is fast:
SELECT SQL_NO_CACHE
*
FROM lt_employee_title AS let
JOIN employee AS ee
ON ee.employee_id = let.employee_id
JOIN company AS com
ON com.company_id = ee.company_id
WHERE let.title_id = 9
AND com.region_code IN('AL', 'AK', 'AZ')
AND com.archived = 0
LIMIT 125;
But these are not:
SELECT SQL_NO_CACHE
COUNT(*)
FROM lt_employee_title AS let
...
SELECT SQL_NO_CACHE SQL_CALC_FOUND_ROWS
*
FROM lt_employee_title AS let
...
The difference of adding any type of count moves the time from 0.016 seconds to 4.0 seconds. We have about 250,000 titles, and 900,000 companiy entries and over 1 million employee records but will be growing.
When I add in count() or SQL_CALC_FOUND_ROWS or ORDER BY com.number_of_employees_id, the EXPLAIN is using temp tables and file_sort which is why the speed decrease. Is there a way to do this sql more efficiently?
EXPLAIN WITH "SQL_CALC_FOUND_ROWS"
1 SIMPLE company ref PRIMARY,state,created,updated,employee_number,archived state 33 const 210864 Using where; Using filesort
1 SIMPLE employee ref PRIMARY,employee_id,created,updated employee_id 5 lead411_main.company.company_id 1 Using where; Using index
1 SIMPLE lt_employee_title eq_ref unique_field,employee_id,title_id,both,both2 unique_field 8 lead411_main.employee.employee_id,const 1 Using index
EXPLAIN WITH OUT "SQL_CALC_FOUND_ROWS"
1 SIMPLE company index PRIMARY,state,created,updated,employee_number,archived employee 5 986 Using where
1 SIMPLE employee ref PRIMARY,employee_id,created,updated employee_id 5 lead411_main.company.company_id 1 Using where; Using index
1 SIMPLE lt_employee_title eq_ref unique_field,employee_id,title_id,both,both2 unique_field 8 lead411_main.employee.employee_id,const 1 Using index