slow join on table (10k rows) - mysql

i have table actions (30 rows) and passed_actions(10k rows)
actions table:
CREATE TABLE `actions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`category_id` int(10) unsigned NOT NULL,
`author_id` int(10) unsigned NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT 'need for url',
`about` longtext COLLATE utf8_unicode_ci,
`image` text COLLATE utf8_unicode_ci,
`page_title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`page_description` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL,
`active` tinyint(1) NOT NULL DEFAULT '0',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `actions_slug_unique` (`slug`),
KEY `actions_author_id_foreign` (`author_id`),
KEY `actions_category_id_foreign` (`category_id`),
CONSTRAINT `actions_author_id_foreign` FOREIGN KEY (`author_id`) REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `actions_category_id_foreign` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=38 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
and passed_actions (~9500 rows)
CREATE TABLE `passed_actions` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL,
`action_id` int(10) unsigned NOT NULL,
`hash` varchar(6) COLLATE utf8_unicode_ci NOT NULL,
`public` tinyint(1) NOT NULL DEFAULT '1',
`successfully_passed` tinyint(1) NOT NULL DEFAULT '0',
`started_at` timestamp NULL DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `passed_actions_hash_unique` (`hash`),
KEY `passed_actions_user_id_foreign` (`user_id`),
KEY `passed_actions_action_id_foreign` (`action_id`),
CONSTRAINT `passed_actions_action_id_foreign` FOREIGN KEY (`action_id`) REFERENCES `actions` (`id`) ON DELETE CASCADE,
CONSTRAINT `passed_actions_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=25733 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
this query takes 0.3-0.5s:
select
actions.*
from actions
left join passed_actions.action_id = actions.id
group by actions.id
order by count(passed_actions.id) DESC
this affects to response time of my api...
why is this happening? i think that 10k rows is not a large table...
i use default mysql config. My server is 1gb ram and 1 cpu (digital ocean droplet)

Your query is actually reasonable fast. Sometimes a correlated subquery can help:
select a.*
from actions a
order by (select count(*) from passed_actions pa where pa.action_id = a.id) desc;
This can use the index you have defined on passed_actions(action_id).

If all you want off the second table is the count for sorting, as appears to be the case, try (untested, sorry):
select
actions.*
from actions
left join (select action_id, count(*) as passed_count from passed_actions group by action_id) p on actions.action_id = p.action_id
order by passed_count DESC
(I can't see where tests.id is coming from, I'm afraid.)

1- Rebuild the index and update statistics
2- Select Only the column you want to use
3- run this query in a new query and hit "Right click" and Click on "Display Estimated Execution Plan" and view the Missing Index Details and build the required index and run the query again

Related

How to order by calculated column in mysql

It is very slow to order by calculated column. Like this one:
select
`users`.`id`,
`username`,
`about_me`,
(
select
count(*)
from
`interests`
inner join `users_interests` on `interests`.`id` = `users_interests`.`interest_id`
where
`users`.`id` = `users_interests`.`user_id`
and `interest_id` in (1, 2, 4) --this is ids of interests which authenticated user has chosen
) as `interests_count`
from
`users`
order by
`interests_count` desc
So in this query i am ordering users By interests which is best suiting authenticated user. And it is very slow in large data tables. Any ideas what will be alternative and much faster solution. Thanks in advance
EDIT
Users Table
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`roles_id` bigint(20) unsigned NOT NULL DEFAULT 2,
`username` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`about_me` varchar(70) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`status` tinyint(1) NOT NULL DEFAULT 0,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_username_unique` (`username`),
UNIQUE KEY `users_email_unique` (`email`),
KEY `users_roles_id_foreign` (`roles_id`),
CONSTRAINT `users_roles_id_foreign` FOREIGN KEY (`roles_id`) REFERENCES
`user_roles` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50102 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
Interests Table
CREATE TABLE `interests` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL,
`interest_category_id` bigint(20) unsigned NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `interests_interest_category_id_foreign` (`interest_category_id`),
CONSTRAINT `interests_interest_category_id_foreign` FOREIGN KEY
(`interest_category_id`) REFERENCES `interests_categories` (`id`) ON DELETE
CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=61 DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_unicode_ci
Pivot table between Users and Interests
CREATE TABLE `users_interests` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`interest_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `users_interests_user_id_foreign` (`user_id`),
KEY `users_interests_interest_id_foreign` (`interest_id`),
CONSTRAINT `users_interests_interest_id_foreign` FOREIGN KEY (`interest_id`) REFERENCES `interests` (`id`) ON DELETE CASCADE,
CONSTRAINT `users_interests_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=251552 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
Without test data it's difficult to know if this would be faster than your current query, but you might find the use of a Common Table Expression (a.k.a. WITH clause) to be helpful here:
with cteCount AS (select ui.`user_id`,
count(*) as `interests_count`
from `interests` i
inner join `users_interests` ui
on i.`id` = ui.`interest_id`
where ui.`interest_id` in (1, 2, 4))
select u.`id`,
`username`,
`about_me`,
cc.`interests_count`
from `users` u
inner join cteCount cc
on cc.`user_id` = u.`id`
order by
cc.`interests_count` desc
The performance of this query will be affected by the presence or absence of indexes on the appropriate columns. Just glancing at it I'd say you should have the following indexes available:
interests
Index 1: id
users_interests
Index 1: interest_id
users
Index 1: id
Try to directly join and aggregate.
SELECT u.id,
u.username,
u.about_me,
count(ui.interest_id) interests_count
FROM users u
LEFT JOIN users_interests ui
ON ui.user_id = u.id
AND ui.interest_id IN (1, 2, 4)
GROUP BY u.id,
u.username,
u.about_me;
Additionally create an index supporting the aggregation.
CREATE INDEX users_id_username_about_me
ON users
(id,
username,
about_me);

mysql ignore index for order by on join

CREATE TABLE `call_session` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`contact_id` bigint(20) unsigned NOT NULL,
`campaign_id` bigint(20) unsigned DEFAULT NULL,
`user_id` bigint(20) unsigned DEFAULT NULL,
`start_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`type` varchar(16) DEFAULT NULL,
`status` varchar(16) DEFAULT NULL,
`continue_reason_id` bigint(20) unsigned DEFAULT NULL,
`continue_by` bigint(20) unsigned DEFAULT NULL,
`end_reason_id` bigint(20) unsigned DEFAULT NULL,
`comment` varchar(255) DEFAULT '',
`call_count` int(11) NOT NULL DEFAULT '0',
`answered_call_count` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `ix_call_session_contact_id` (`contact_id`),
KEY `ix_call_session_user_id` (`user_id`),
KEY `ix_call_session_campaign_id` (`campaign_id`),
KEY `call_session_continue_by_foreign` (`continue_by`),
KEY `call_session_end_reason_id_foreign` (`end_reason_id`),
KEY `ix_call_session_end_time` (`end_time`),
KEY `ix_call_session_start_time` (`start_time`),
KEY `ix_call_session_continue_reason_id` (`continue_reason_id`),
CONSTRAINT `call_session_campaign_id_foreign` FOREIGN KEY (`campaign_id`) REFERENCES `campaign` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `call_session_continue_by_foreign` FOREIGN KEY (`continue_by`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `call_session_continue_reason_id_foreign` FOREIGN KEY (`continue_reason_id`) REFERENCES `continue_reason` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `call_session_end_reason_id_foreign` FOREIGN KEY (`end_reason_id`) REFERENCES `end_reason` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `call_session_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
)
CREATE TABLE `continue_reason` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`project_id` bigint(20) unsigned DEFAULT NULL,
`sort` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `ix_continue_reason_project_id` (`project_id`),
CONSTRAINT `continue_reason_project_id_foreign` FOREIGN KEY (`project_id`) REFERENCES `project` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
)
The call_session table contain more than 500,000 rows.
The continue_reason table contain only one row.
When I join the continue_reason table for the name column, mysql ignore the start_date index so the query takes between 1.8 to 3 seconds.
I can't figure out why.
The problematic query is:
SELECT `call_session`.`id`,
`continue_reason`.`name`
FROM `call_session`
LEFT JOIN `continue_reason` ON `continue_reason`.`id` = `call_session`.`continue_reason_id`
ORDER BY `call_session`.`start_time` DESC LIMIT 1;
Explain result:
Same query without join works great:
SELECT `call_session`.`id`,
(SELECT `name` FROM `continue_reason` WHERE id = `call_session`.`continue_reason_id`) AS `continue_reason.name`
FROM `call_session`
ORDER BY `call_session`.`start_time` DESC LIMIT 1;
Explain results:
I found many similar questions but I don't have any DISTINCT, no GROUP BY, and there is a very clear benefit for using the index.
Any thoughts?
Thanks in advance
P.S. Force index didn't help.
Here is the Optimizer report

What is the better way to select minimum value within join?

I've three tables where I want to select the oldest media according to date from a joined table. The query is working fine but I want to know if its correct solution for performance or there is a better way to do this.
SELECT product.`product_id`, product.`title`, product.`price`,
media_gallery.`thumbnail`, product.`special_price`
FROM product
LEFT JOIN
(SELECT product_media.`product_id`, product_media.`media_id`
FROM product_media ORDER BY product_media.`added_on` ASC LIMIT 1) product_media
ON product.`product_id` = product_media.`product_id`
LEFT JOIN media_gallery ON product_media.`media_id` = media_gallery.`media_id`
GROUP BY product.`product_id`
LIMIT 10
I've three tables from which I want to select the products with single image and the image should be the oldest. The image is stored in media_gallery and the image is assigned in product_media. The product can have multiple images which are referenced in product_media. I want to select product_id, product.title, product.price, product.special_price, media_gallery.thumbnail. The thumbnail should be the image added as earliest
CREATE TABLE `product_media` (
`id` varchar(60) NOT NULL,
`product_id` varchar(50) NOT NULL,
`media_id` varchar(50) NOT NULL,
`added_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `product_id_2` (`product_id`,`media_id`),
KEY `product_id` (`product_id`),
KEY `media_id` (`media_id`),
CONSTRAINT `product_media_ibfk_1` FOREIGN KEY (`product_id`) REFERENCES `product` (`product_id`) ON DELETE CASCADE ON UPDATE NO ACTION,
CONSTRAINT `product_media_ibfk_2` FOREIGN KEY (`media_id`) REFERENCES `media_gallery` (`media_id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `media_gallery` (
`media_id` varchar(50) NOT NULL,
`media_type` varchar(10) NOT NULL,
`media_title` varchar(100) DEFAULT NULL,
`media_url` varchar(255) NOT NULL,
`thumbnail` varchar(255) NOT NULL,
`added_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`media_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
CREATE TABLE `product` (
`product_id` varchar(50) NOT NULL,
`title` varchar(255) NOT NULL,
`description` varchar(2000) DEFAULT NULL,
`price` decimal(10,2) NOT NULL,
`special_price` decimal(10,2) DEFAULT NULL,
`status` varchar(15) DEFAULT 'AVAILABLE',
`weight` double DEFAULT NULL,
`unit_id` varchar(50) DEFAULT NULL,
`added_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`product_id`),
UNIQUE KEY `sku` (`sku`),
KEY `unit_id` (`unit_id`),
CONSTRAINT `product_ibfk_1` FOREIGN KEY (`unit_id`) REFERENCES `units` (`unit_id`) ON DELETE SET NULL ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Index not being used for sort in joined view

I have the following schema:
CREATE TABLE `news` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`news_category_id` int(10) unsigned NOT NULL,
`news_type_id` int(10) unsigned NOT NULL,
`news_pictures_main_id` int(10) unsigned DEFAULT NULL,
`title` tinytext COLLATE latin1_general_ci,
`body` text COLLATE latin1_general_ci,
`tmstp` timestamp NULL DEFAULT NULL,
`subcategory` varchar(64) COLLATE latin1_general_ci DEFAULT NULL,
`source` varchar(128) COLLATE latin1_general_ci DEFAULT NULL,
`old_id` int(10) unsigned DEFAULT NULL,
`tags` text COLLATE latin1_general_ci,
PRIMARY KEY (`id`),
KEY `news_time_idx` (`tmstp`),
KEY `fk_news_news_pictures1` (`news_pictures_main_id`),
KEY `fk_news_news_category1` (`news_category_id`),
KEY `fk_news_news_type1` (`news_type_id`),
CONSTRAINT `fk_news_news_category1` FOREIGN KEY (`news_category_id`) REFERENCES `news_category` (`id`) ON UPDATE CASCADE,
CONSTRAINT `fk_news_news_pictures1` FOREIGN KEY (`news_pictures_main_id`) REFERENCES `news_pictures` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `fk_news_news_type1` FOREIGN KEY (`news_type_id`) REFERENCES `news_type` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB
CREATE TABLE `news_pictures` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`path` text COLLATE latin1_general_ci,
`description` text COLLATE latin1_general_ci,
`author` varchar(45) COLLATE latin1_general_ci DEFAULT NULL,
`news_id` int(10) unsigned DEFAULT NULL,
`temp_id` varchar(40) COLLATE latin1_general_ci DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `filename_old_id_unq` (`path`(20),`temp_id`(6)),
KEY `fk_news_pictures_news1` (`news_id`),
KEY `temp_id_idx` (`temp_id`(8)),
CONSTRAINT `fk_news_pictures_news1` FOREIGN KEY (`news_id`) REFERENCES `news` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB
CREATE TABLE `news_category` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE latin1_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
CREATE TABLE `news_type` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE latin1_general_ci DEFAULT NULL,
`slug` varchar(45) COLLATE latin1_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
KEY `news_type_slug_idx` (`slug`)
) ENGINE=InnoDB
From that, there is derived the following view:
CREATE OR REPLACE VIEW `news_full` AS select `n`.`id` AS `id`,
`n`.`title` AS `title`,
`n`.`body` AS `body`,
`n`.`tmstp` AS `tmstp`,
`n`.`subcategory` AS `subcategory`,
`n`.`source` AS `source`,
`n`.`old_id` AS `old_id`,
`n`.`news_type_id` AS `news_type_id`,
`n`.`tags` AS `tags`,
`nt`.`name` AS `news_type_name`,
`nt`.`slug` AS `news_type_slug`,
`n`.`news_pictures_main_id` AS `news_pictures_main_id`,
`np`.`path` AS `news_pictures_main_path`,
`np`.`description` AS `news_pictures_main_description`,
`np`.`author` AS `news_pictures_main_author`,
`np`.`temp_id` AS `news_pictures_main_temp_id`,
`n`.`news_category_id` AS `news_category_id`,
`nc`.`name` AS `news_category_name`
from (((`news` `n`
left join `news_pictures` `np` on((`n`.`news_pictures_main_id` = `np`.`id`)))
join `news_category` `nc` on((`n`.`news_category_id` = `nc`.`id`)))
join `news_type` `nt` on((`n`.`news_type_id` = `nt`.`id`)));
However, if I try to run the following query:
select * from news_full order by tmstp limit 100
I get the following execution plan (please click on the image to expand it):
Notice the Using temporary; Using filesort field in the first step. But this is weird, because tmstp field is indexed on the base table.
First I thought this was due the left join on the view, but I've changed it to inner join and I got the same results.
Edit
As #Michael-sqlbot cleverly noticed, the query optimizer is inverting the order of the base tables, putting news_category (nc) first.
If I change the query that creates the view to use only LEFT JOINs it seems to work:
The execution times, as expected, as blatantly different:
Not satisfied, I created another view with the original query, adding the STRAIGHT_JOIN statement. So, the query plan comes as follows:
So, it's not using the index.
However, if I run the plan for the base query adding the same ORDER BY and LIMIT clauses, it does uses the index:
(Not an answer, but some other issues to bring up...)
UNIQUE KEY `filename_old_id_unq` (`path`(20),`temp_id`(6))
That constrains the first 20 characters of path, together with the first 6 characters of temp_id to be unique across the table. Did you really want that?
I suspect the optimizer will never use both columns of that index. (In general, prefixing is useless.)
And...
`title` tinytext COLLATE latin1_general_ci
Change to VARCHAR(255). There are disadvantages of TINYTEXT and perhaps no advantages.

Put unique index across more than one table

I have run into a problem where the easiest solution would be to add a unique index that goes across multiple tables in mysql. Is this possible?
For the purpose of this question I will have three tables; status, tracks and events. The status and tracks tables both have an auto incrementing ID (T_ID or S_ID) in. Information from them is added to the events table with a trigger. The problem is that there could be the same auto incrementing ID in tracks and status, this means that their could be the same ID more than once in events.
tracks;
CREATE TABLE `tracks` (
`ID` int(11) NOT NULL,
`url` varchar(200) COLLATE latin1_general_ci NOT NULL,
`name` varchar(100) COLLATE latin1_general_ci NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
`T_ID` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`T_ID`),
UNIQUE KEY `url` (`url`),
UNIQUE KEY `T_ID` (`T_ID`),
KEY `ID` (`ID`),
CONSTRAINT `tracks_ibfk_1` FOREIGN KEY (`ID`) REFERENCES `members` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
status
CREATE TABLE `status` (
`ID` int(11) NOT NULL,
`status` varchar(300) COLLATE latin1_general_ci NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
`S_ID` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`S_ID`),
UNIQUE KEY `S_ID` (`S_ID`),
KEY `ID` (`ID`),
CONSTRAINT `status_ibfk_1` FOREIGN KEY (`ID`) REFERENCES `artists` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci
events
CREATE TABLE `events` (
`ID` int(11) NOT NULL,
`action` varchar(100) COLLATE latin1_general_ci NOT NULL,
`timestamp` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
`E_ID` int(11) NOT NULL,
KEY `ID` (`ID`),
CONSTRAINT `events_ibfk_1` FOREIGN KEY (`ID`) REFERENCES `members` (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci COMMENT='This table shows what the artist has done and is used feed'
Agreed, you cannot have an index across multiple tables.
However, consider changing your schema by adding a "source" column to your event table and making a unique constraint on the combination of the source and the foreign key. This would prevent duplicate keys being inserted from the same source.