MySQL : SELECT on big table takes a lot of time. Solutions?

Viewed 79

my app get stuck for hours on simple queries like :

SELECT COUNT(*) FROM `item`

Context :

  • This table is around 200Gb+ and 50M+ rows.
  • We have a RDS on AWS with 2CPU and 16GiB RAM (db.r6g.large).

This is the table structure SQL dump :

/*

 Target Server Type    : MySQL
 Target Server Version : 80023
 File Encoding         : 65001

*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `item`;
CREATE TABLE `item` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `status` tinyint DEFAULT '1',
  `source_id` int unsigned DEFAULT NULL,
  `type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `url` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `title` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `sku` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `price` decimal(20,4) DEFAULT NULL,
  `price_bc` decimal(20,4) DEFAULT NULL,
  `price_original` decimal(20,4) DEFAULT NULL,
  `currency` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
  `image` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `time_start` datetime DEFAULT NULL,
  `time_end` datetime DEFAULT NULL,
  `block_update` tinyint(1) DEFAULT '0',
  `status_api` tinyint(1) DEFAULT '1',
  `data` json DEFAULT NULL,
  `created_at` int unsigned DEFAULT NULL,
  `updated_at` int unsigned DEFAULT NULL,
  `retailer_id` int DEFAULT NULL,
  `hash` char(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `count_by_hash` int DEFAULT '1',
  `item_last_update` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `sku_retailer_idx` (`sku`,`retailer_id`),
  KEY `updated_at_idx` (`updated_at`),
  KEY `time_end_idx` (`time_end`),
  KEY `retailer_id_idx` (`retailer_id`),
  KEY `hash_idx` (`hash`),
  KEY `source_id_hash_idx` (`source_id`,`hash`) USING BTREE,
  KEY `count_by_hash_idx` (`count_by_hash`) USING BTREE,
  KEY `created_at_idx` (`created_at`) USING BTREE,
  KEY `title_idx` (`title`),
  KEY `currency_idx` (`currency`),
  KEY `price_idx` (`price`),
  KEY `retailer_id_title_idx` (`retailer_id`,`title`) USING BTREE,
  KEY `source_id_idx` (`source_id`) USING BTREE,
  KEY `source_id_count_by_hash_idx` (`source_id`,`count_by_hash`) USING BTREE,
  KEY `status_idx` (`status`) USING BTREE,
  CONSTRAINT `fk-source_id` FOREIGN KEY (`source_id`) REFERENCES `source` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1858202585 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

SET FOREIGN_KEY_CHECKS = 1;
  1. does partitioning the table could help on a simple query like this ?
  2. do I need to increase the RAM of the RDS ? If yes what configuration do I need ?
  3. is NoSQL more compatible to this kind of structure ?
  4. Do you have any advices/solutions/fixes so the app can run those queries (we would like to keep all the data and not erase it if possible..) ?
2 Answers

"SELECT COUNT(*) FROM item" needs to scan an index. The smallest index is about 200MB, so that seems like it should not take "minutes".

There are probably multiple queries that do full table scans. Such will bump out all the cached data from the ~11GB of cache (the buffer_pool) and do that about 20 times. That's a lot of I/O and a lot of elapsed time. Meanwhile, most other queries will run slowly because their cached data is being bumped out.

The resolution:

  1. Locate these naughty queries. RDS probably gives you access to the "slowlog".
  2. Grab the slowlog and run pt-query-digest or mysqldumpslow -s t to find the "worst" queries.
  3. Then we can discuss them.

There are some redundant indexes; removing them won't solve the problem. A rule: If you have INDEX(a), INDEX(a,b), you don't need the former.

If hash is some kind of scrambled value, it is likely that a single-row lookup (or update) will require a disk hit (and bump something else out of the cache).

decimal(20,4) takes 10 bytes and allows values up to 9,999,999,999,999,999.9999; that seems excessive. (Shrinking it won't save much space; something to keep in mind for the future.)

I see that AUTO_INCREMENT has reached 1.8 billion. If there are only 50M rows, does the processing do a lot of DELETEs? Or maybe REPLACE``? IODKU is better than REPLACE`.

Thanks for all the advices here, but the problem was that we were using the MySQL json type for a very heavy column. Removing this column or even changing it to varchar made the COUNT(id) around 1000x faster (also adding WHERE id > 1 helped..)

Note : it was impossible to just delete the column as it was, we had to change it to varchar before.

Related