Deletion using indexed field is slow in mysql - mysql

I have two tables PARENT and CHILD. PARENT has one to many relationship with CHILD. PARENT table has 300000 and CHILD has 1500000 rows respectively. Row with some ID is inserted and deleted frequently and deletion takes time approximately 20 sec. And Execution Plan of deletion in CHILD table shows entire table scan. Why deletion on indexed field takes time?
UPDATE - 1
Table schema of parent and child
show create table parent;
| Table | Create Table | parent | CREATE TABLE `parent` (
`id` bigint(20) NOT NULL,
`name` varchar(500) COLLATE latin1_general_cs DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_cs |
show create table child;
| Table | Create Table | child | CREATE TABLE `child` (
`id` bigint(20) NOT NULL,
`value` varchar(2000) COLLATE latin1_general_cs DEFAULT NULL,
KEY `child_cons` (`id`),
CONSTRAINT `child_cons` FOREIGN KEY (`id`) REFERENCES `parent` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_general_cs |
UPDATE - 2
The delete statement used is
delete from child where id=1;
Execution plan
explain delete from child where id=1;
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| 1 | SIMPLE | child | ALL | child_cons | NULL | NULL | NULL | 1350398 | Using where |
1 row in set (0.00 sec)
UPDATE - 3
Child table contains only 5 rows for id = 1

Related

Optimize MySQL query with ORDER BY and INNER JOIN (select where user following)

I need to optimize the following query:
SELECT a.*
FROM Activity AS a
JOIN users_following AS f1
ON f1.userId = a.originatorId
AND f1.followerId = 1
ORDER
BY a.time DESC
LIMIT 10
The idea is to get all activity originated by users some user (in this case, user 1) is following, sorted by time. This query as written is very slow (~5s), though it's very quick if either a) the join is omitted or b) the order by clause is omitted.
Things I've tried:
WHERE ... IN as opposed to INNER JOIN
Here are the CREATE TABLE and EXPLAIN definitions.
CREATE TABLE `Activity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` int(11) NOT NULL,
`userId` int(11) NOT NULL,
`voteId` int(11) DEFAULT NULL,
`commentId` int(11) DEFAULT NULL,
`achievementId` int(11) DEFAULT NULL,
`challengeId` int(11) DEFAULT NULL,
`followerId` int(11) DEFAULT NULL,
`acknowledged` int(11) NOT NULL DEFAULT '0',
`type` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`isPrivate` int(11) NOT NULL DEFAULT '0',
`portalId` int(11) DEFAULT NULL,
`postId` int(11) DEFAULT NULL,
`portalMemberId` int(11) DEFAULT NULL,
`originatorId` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_55026B0C1CC880D8` (`portalMemberId`),
KEY `IDX_55026B0C1D79C36A` (`challengeId`),
KEY `IDX_55026B0CE7A069D0` (`achievementId`),
KEY `IDX_55026B0CB6FEC0EE` (`voteId`),
KEY `IDX_55026B0C6690C3F5` (`commentId`),
KEY `IDX_55026B0C64B64DCC` (`userId`),
KEY `IDX_55026B0CF542AA03` (`followerId`),
KEY `IDX_55026B0C57076B1F` (`portalId`),
KEY `IDX_55026B0CE094D20D` (`postId`),
KEY `IDX_55026B0C162E014D` (`originatorId`),
KEY `activity_time_idx` (`time`),
KEY `activity_filter_idx` (`type`,`originatorId`,`userId`,`isPrivate`),
KEY `acknowledged_idx` (`acknowledged`),
KEY `idx1` (`time`,`originatorId`),
KEY `idx2` (`originatorId`,`userId`,`postId`,`challengeId`,`commentId`,`time`),
CONSTRAINT `FK_55026B0C162E014D` FOREIGN KEY (`originatorId`) REFERENCES `ProseUser` (`id`),
CONSTRAINT `FK_55026B0C1CC880D8` FOREIGN KEY (`portalMemberId`) REFERENCES `PortalMember` (`id`),
CONSTRAINT `FK_55026B0C1D79C36A` FOREIGN KEY (`challengeId`) REFERENCES `Challenge` (`id`),
CONSTRAINT `FK_55026B0C57076B1F` FOREIGN KEY (`portalId`) REFERENCES `Portal` (`id`),
CONSTRAINT `FK_55026B0C64B64DCC` FOREIGN KEY (`userId`) REFERENCES `ProseUser` (`id`),
CONSTRAINT `FK_55026B0C6690C3F5` FOREIGN KEY (`commentId`) REFERENCES `Comment` (`id`),
CONSTRAINT `FK_55026B0CB6FEC0EE` FOREIGN KEY (`voteId`) REFERENCES `Vote` (`id`),
CONSTRAINT `FK_55026B0CE094D20D` FOREIGN KEY (`postId`) REFERENCES `Post` (`id`),
CONSTRAINT `FK_55026B0CE7A069D0` FOREIGN KEY (`achievementId`) REFERENCES `UserAchievement` (`id`),
CONSTRAINT `FK_55026B0CF542AA03` FOREIGN KEY (`followerId`) REFERENCES `ProseUser` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4097200 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
CREATE TABLE `users_following` (
`userId` int(11) NOT NULL,
`followerId` int(11) NOT NULL,
PRIMARY KEY (`userId`,`followerId`),
KEY `IDX_17C2F70264B64DCC` (`userId`),
KEY `IDX_17C2F702F542AA03` (`followerId`),
KEY `idx1` (`userId`,`followerId`),
KEY `idx2` (`followerId`,`userId`),
CONSTRAINT `FK_17C2F70264B64DCC` FOREIGN KEY (`userId`) REFERENCES `ProseUser` (`id`),
CONSTRAINT `FK_17C2F702F542AA03` FOREIGN KEY (`followerId`) REFERENCES `ProseUser` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
EXPLAIN
+----+-------------+-------+------------+------+-------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
| 1 | SIMPLE | f1 | NULL | ref | PRIMARY,IDX_17C2F70264B64DCC,IDX_17C2F702F542AA03,idx1,idx2 | IDX_17C2F702F542AA03 | 4 | const | 145 | 100.00 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | a | NULL | ref | IDX_55026B0C162E014D,idx2 | IDX_55026B0C162E014D | 5 | prose_2_24_2021.f1.userId | 38 | 100.00 | NULL |
+----+-------------+-------+------------+------+-------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
Let's make an example: user #1 follows users #2 and #3. Here are activities ordered by users, then time:
+------+-------+
| user | time |
+------+-------+
| 1 | 10:00 |
| 1 | 11:00 |
| 2 | 10:00 |
| 2 | 12:00 |
| 3 | 09:00 |
| 3 | 13:00 |
| 4 | 10:00 |
+------+-------+
We can quickly find the followed users' activities, but get the times unordered:
+------+-------+
| user | time |
+------+-------+
| 2 | 10:00 |
| 2 | 12:00 |
| 3 | 09:00 |
| 3 | 13:00 |
+------+-------+
This means that we must sort them in order to get the top n sorted. And if these were not four but thousands or millions of activities that would take awfully long.
If on the other hand the data were available ordered by time descending, then user:
+------+-------+
| user | time |
+------+-------+
| 3 | 13:00 |
| 2 | 12:00 |
| 1 | 11:00 |
| 1 | 10:00 |
| 2 | 10:00 |
| 4 | 10:00 |
| 3 | 09:00 |
+------+-------+
We would have to read the whole data sequentially until we found the n top activities. No further ordering necessary. If we are lucky the first n rows are matches and that's it. If we are unlucky we read the whole table (or index for that matter).
So, there is no guarantee to get this quick. The first approach gets the data quick, but sorting can take long. The second approach needs no sorting, but reading can take long.
I like the second approach a tad better, but it all depends on the data of course. Are there many users? Is the majority followed by user #1 or only few? Are there many activities? Many by few users or few activities per user? ... Anyway, I'd provide this index:
create index idx1 on activity (time desc, originatorid);
As an index is just an offer to the DBMS we might just as well provide the other index for the case the DBMS wants to follow the other route:
create index idx2 on activity (originatorid, time desc);
And this is how I'd probably write the query:
SELECT a.*
FROM activity AS a
WHERE EXISTS
(
SELECT NULL
FROM users_following AS f1
WHERE f1.userId = a.originatorId
AND f1.followerId = 1
)
ORDER BY a.time DESC
LIMIT 10;

MySQL ON DUPLICATE KEY UPDATE only inserting, not updating

I have dug through SO questions and none address my specific issue ... I have read the following relevant threads: Here, to no avail.
I have a simple table with the following data:
|-----------------------------------------------------------------------------|
| id | contractor_id | section_id | page_id | modified | timestamp |
|-----------------------------------------------------------------------------|
Here is the create statement:
CREATE TABLE `reusable_page_modified` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`contractor_id` int(11) DEFAULT NULL,
`section_id` int(11) DEFAULT NULL,
`page_id` int(11) DEFAULT NULL,
`modified` int(1) NOT NULL DEFAULT '1',
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13844 DEFAULT CHARSET=utf8;
Now I have 4 rows in the db right now, and they are (I'll leave off id and timestamp since they are auto generated):
|---------------------------------------------------------|
| contractor_id | section_id | page_id | modified |
###########################################################
| 1016 | 309 | 10303 | 0 |
|----------------------------------------------------------
| 1017 | 309 | 10303 | 1 |
|----------------------------------------------------------
| 1073 | 309 | 10303 | 1 |
|----------------------------------------------------------
| 240 | 309 | 10303 | 1 |
|----------------------------------------------------------
I am focusing on the first line where modified is set to 0. What I want to do is set it to 1 if contractor_id, section_id and page_id all exist. If not, enter a new row.
This is what I have:
INSERT INTO `reusable_page_modified`
(contractor_id, section_id, page_id, modified)
VALUES ('1016', '309', '10303', '1')
ON DUPLICATE KEY UPDATE
`section_id` = '309'
AND `page_id` = '10303'
AND `contractor_id` = '1016';
This creates a new row. I think I am not understanding the ON DUPLICATE KEY UPDATE statment the way it was intended. I have read the MySQL documentation Here and still no help. What am I not seeing here?
You are not far from being correct, but your syntax is a bit off. First, if you want ON DUPLICATE KEY to work when a record with duplicate values for the contractor_id, section_id, and page_id columns is inserted, you will need a unique constraint on these three columns. You can add one using the following:
ALTER TABLE reusable_page_modified
ADD CONSTRAINT u_cnst UNIQUE (contractor_id, section_id, page_id);
Next, for the actual INSERT statement you would use what you have is close, except that you update all the columns which don't require any updating. Instead, leave the three columns alone and instead update the modified column to 1:
INSERT INTO reusable_page_modified
(contractor_id, section_id, page_id, modified)
VALUES ('1016', '309', '10303', '1')
ON DUPLICATE KEY UPDATE
modified = 1

Have one entry automatically delete when its key is deleted mysql

I've got a user table with an id column
a profile table with an id column and a user_id col which refers to id in the user table
I want to automatically delete the profile when I delete the user.
Is this possible in mysql?
Assume that there is 1 to N relationship in between PARENT and CHILD entities so when you delete a PARENT then the associated CHILD(s) will be deleted as you wanted. For that you use ON DELETE CASCADE like below.
What you need to be aware of is, depending on your design if the CHILD entity is associated with one or more entities then you might cause unexpected issue such as breaking relationships, removing records in associated tables etc. Also read up on the storage engines in link above as well, you cannot use MuISAM.
PARENT
CREATE TABLE `parent` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CHILD
CREATE TABLE `child` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE utf8_unicode_ci NOT NULL,
`parent_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `FK_1_idx` (`parent_id`),
CONSTRAINT `FK_1` FOREIGN KEY (`parent_id`) REFERENCES `parent` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
EXAMPLE
Populate Parent
mysql> INSERT INTO parent (name) VALUES ('parent_1'), ('parent_2');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM parent;
+----+----------+
| id | name |
+----+----------+
| 1 | parent_1 |
| 2 | parent_2 |
+----+----------+
2 rows in set (0.00 sec)
Populate Child
mysql> INSERT INTO child (name, parent_id) VALUES ('child_1', 1), ('child_2', 1), ('child_3', 2);
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM child;
+----+---------+-----------+
| id | name | parent_id |
+----+---------+-----------+
| 1 | child_1 | 1 |
| 2 | child_2 | 1 |
| 3 | child_3 | 2 |
+----+---------+-----------+
3 rows in set (0.00 sec)
Delete
mysql> DELETE FROM parent WHERE name = 'parent_1';
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM child;
+----+---------+-----------+
| id | name | parent_id |
+----+---------+-----------+
| 3 | child_3 | 2 |
+----+---------+-----------+
1 row in set (0.00 sec)
You can do this ON DELETE CASCADE command in the table definition.
See: MySQL foreign key constraints, cascade delete
Or you can use a trigger to do the same:
CREATE
TRIGGER `delete_from_profile`
AFTER DELETE ON `user`
FOR EACH ROW
BEGIN
DELETE
FROM profile
WHERE user_id = old.id;
END

file sort being used for query and I don't see why (mysql 5)

mysql> EXPLAIN SELECT * FROM
(`phppos_modules`)
JOIN `phppos_permissions` ON `phppos_permissions`.`module_id`=`phppos_modules`.`module_id`
WHERE `phppos_permissions`.`person_id` = '1'
ORDER BY `sort` asc;
+----+-------------+--------------------+--------+-------------------+---------+---------+------------------------------------+------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+--------------------+--------+-------------------+---------+---------+------------------------------------+------+----------------+
| 1 | SIMPLE | phppos_modules | ALL | PRIMARY | NULL | NULL | NULL | 11 | Using filesort |
| 1 | SIMPLE | phppos_permissions | eq_ref | PRIMARY,person_id | PRIMARY | 306 | pos.phppos_modules.module_id,const | 1 | Using index |
+----+-------------+--------------------+--------+-------------------+---------+---------+------------------------------------+------+----------------+
2 rows in set (0.00 sec)
mysql> show create table phppos_modules;
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| phppos_modules | CREATE TABLE `phppos_modules` (
`name_lang_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`desc_lang_key` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`sort` int(10) NOT NULL,
`icon` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`module_id` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`module_id`),
UNIQUE KEY `desc_lang_key` (`desc_lang_key`),
UNIQUE KEY `name_lang_key` (`name_lang_key`),
KEY `sort` (`sort`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
+----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql>
mysql> show create table phppos_permissions;
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| phppos_permissions | CREATE TABLE `phppos_permissions` (
`module_id` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`person_id` int(10) NOT NULL,
PRIMARY KEY (`module_id`,`person_id`),
KEY `person_id` (`person_id`),
CONSTRAINT `phppos_permissions_ibfk_1` FOREIGN KEY (`person_id`) REFERENCES `phppos_employees` (`person_id`),
CONSTRAINT `phppos_permissions_ibfk_2` FOREIGN KEY (`module_id`) REFERENCES `phppos_modules` (`module_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci |
+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
Likely you are being affected by this condition from the documentation:
The key used to fetch the rows is not the same as the one used in the
ORDER BY:
SELECT * FROM t1 WHERE key2=constant ORDER BY key1;
Try the following:
ALTER TABLE `phppos_modules`
ADD INDEX `all_w_sort_first` (`sort`,`module_id`,`name_lang_key`,`desc_lang_key`,`icon`);

MySQL won't use index for query?

I have this table:
CREATE TABLE `point` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`siteid` INT(11) NOT NULL,
`lft` INT(11) DEFAULT NULL,
`rgt` INT(11) DEFAULT NULL,
`level` SMALLINT(6) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `point_siteid_site_id` (`siteid`),
CONSTRAINT `point_siteid_site_id` FOREIGN KEY (`siteid`) REFERENCES `site` (`id`) ON DELETE CASCADE
) ENGINE=INNODB AUTO_INCREMENT=35 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
And this query:
SELECT * FROM `point` WHERE siteid = 1;
Which results in this EXPLAIN information:
+----+-------------+-------+------+----------------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+----------------------+------+---------+------+------+-------------+
| 1 | SIMPLE | point | ALL | point_siteid_site_id | NULL | NULL | NULL | 6 | Using where |
+----+-------------+-------+------+----------------------+------+---------+------+------+-------------+
Question is, why isn't the query using the point_siteid_site_id index?
I haven't used MySQL much, but in PostgreSQL if the number of records is small in the table, it can decide not to use the index. This is not a problem, because it chooses the best query plan for the situation. When the number of records is bigger, it will use the index. Maybe this is the same case here with MySQL.
Edit: Are you sure the foreign key is indexed?
It's probably because you don't have enough sample data, or the cardinality (diversity) of the siteid values isn't very large. These are the most common reasons the optimizer will find it quicker just to read all the values.