This question already has answers here:
MySQL varchar index length
(2 answers)
Closed 1 year ago.
Note:
First I want to mention that I read all other related questions and answers.
Question:
I had created 2 indexes for the table I have but whenever I use my queries it's not using the index. even when forced to use the index it's not using it.
The table has 1.5M rows and it will be increased, and the query is taking 35+ seconds.
Query 1
explain analyze
SELECT sum(cid_user_usd_earned)
FROM `bbtv_adv_records`
WHERE (user_id =2
and `cid_assign_month` = '2020-08-01'
And `content_type` = 'UGC'
);
explain analyze
SELECT sum(cid_user_usd_earned)
FROM `bbtv_adv_records`
USE INDEX (bbtv_adv_records_user_id_cid_assign_month_content_type_index)
WHERE (user_id =2
and `cid_assign_month` = '2020-08-01'
And `content_type` = 'UGC'
);
Query 2
explain analyze
SELECT *
FROM `bbtv_adv_records`
WHERE (user_id =2
and `cid_assign_month` = '2020-08-01'
);
Query 3
explain analyze
SELECT sum(cid_user_usd_earned),channel_id
FROM `bbtv_adv_records`
WHERE (user_id =2
and `cid_assign_month` = '2020-08-01'
Group by `channel_id`
);
Table
CREATE TABLE `bbtv_adv_records` (
`id` bigint UNSIGNED NOT NULL,
`content_type` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
....
`channel_id` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
...
`cid_assign_month` date NOT NULL,
`cid_process_state` enum('process','done','fail') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
--
-- Indexes for table `bbtv_adv_records`
--
ALTER TABLE `bbtv_adv_records`
ADD PRIMARY KEY (`id`),
ADD KEY `bbtv_adv_records_user_id_cid_assign_month_index
` (`user_id`,`cid_assign_month`),
ADD KEY `bbtv_adv_records_user_id_cid_assign_month_content_type_index`
(`user_id`,`cid_assign_month`,`content_type`);
--
-- AUTO_INCREMENT for table `bbtv_adv_records`
--
ALTER TABLE `bbtv_adv_records`
MODIFY `id` bigint UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1543191;
COMMIT;
mysql> show index from bbtv_adv_records
+------------------+------------+--------------------------------------------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+------------------+------------+--------------------------------------------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| bbtv_adv_records | 0 | PRIMARY | 1 | id | A | 1456578 | NULL | NULL | | BTREE | | | YES | NULL |
| bbtv_adv_records | 1 | bbtv_adv_records_user_id_cid_assign_month_index | 1 | user_id | A | 8 | NULL | NULL | YES | BTREE | | | YES | NULL |
| bbtv_adv_records | 1 | bbtv_adv_records_user_id_cid_assign_month_index | 2 | cid_assign_month | A | 47 | NULL | NULL | | BTREE | | | YES | NULL |
| bbtv_adv_records | 1 | bbtv_adv_records_user_id_cid_assign_month_content_type_index | 1 | user_id | A | 8 | NULL | NULL | YES | BTREE | | | YES | NULL |
| bbtv_adv_records | 1 | bbtv_adv_records_user_id_cid_assign_month_content_type_index | 2 | cid_assign_month | A | 49 | NULL | NULL | | BTREE | | | YES | NULL |
| bbtv_adv_records | 1 | bbtv_adv_records_user_id_cid_assign_month_content_type_index | 3 | content_type | A | 58 | NULL | NULL | YES | BTREE | | | YES | NULL |
+------------------+------------+--------------------------------------------------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
6 rows in set (0.01 sec)
note the best result I have 10 seconds when creating only the cid_assign_month index alone, but all my queries have the cid_assign_month with the user_id always.
could you show the result of "show index from bbtv_adv_records ".
The mysql query optimizer is a cost based optimiser.
It try to find out the best execution plan.
If the cost of ALL( full table scan) is less then REF_OR_NULL (using secondary index fetch data), It will use full table scan where time complexity is O(n) .
for example, there are the commonest cost matrix
the IO cost of Reading 1 block page is 1.
the CPU cost of comparing 1 record is 0.2.
the query 1:
SELECT sum(cid_user_usd_earned) FROM bbtv_adv_records WHERE (user_id =2 and cid_assign_month = '2020-08-01' And content_type = 'UGC');
because the column cid_user_usd_earned is not in secondary index bbtv_adv_records_user_id_cid_assign_month_content_type_index, mysql will return to the primary index to get cid_user_usd_earned, after using binary search in secondary index.
Solved
It looks that MySQL indexing will not work with varchar(400) or any large length.
change from
`content_type` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`channel_id` varchar(400) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
to
`content_type` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`channel_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`user_id` bigint UNSIGNED DEFAULT NULL,
now the Query show indexing and faster from 34s to 8s
mysql> explain analyze SELECT sum(cid_user_usd_earned) FROM `bbtv_adv_records` WHERE (user_id =2 and `cid_assign_month` = '2020-08-01' And `content_type` = 'UGC');
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Aggregate: sum(bbtv_adv_records.cid_user_usd_earned) (actual time=8247.975..8247.981 rows=1 loops=1)
-> Index lookup on bbtv_adv_records using bbtv_adv_records_user_id_cid_assign_month_content_type_index (user_id=2, cid_assign_month=DATE'2020-08-01', content_type='UGC') (cost=116370.56 rows=496622) (actual time=0.373..6175.144 rows=259373 loops=1)
|
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (8.25 sec)
Related
I initially selected part of the stock_hfq table, and then filtered it with ts_code.
I used the intermediate table stock_hfq_temp to filter the stock_hfq data. The data queried out is about 2M rows. It takes 2min 1s without exists, and 1min 5s with exists.However, adding the time to write the temporary table stock_hfq_temp and the time to create the index ts_code of the temporary table, the total time difference is only 4s.
Is there any other way to speed up my query speed?
ts_code is unique in the stock_hfq_temp table.
The relevant sentences and results are as follows:
select * from stock_hfq t where t.trade_date>'20110302';
Wall time: 2min 12s
select ts_code from stock_hfq_temp b
Wall time: 8 ms
select * from stock_hfq t where t.trade_date>'20110302' and exists (select 1 from stock_hfq_temp b where b.ts_code=t.ts_code);
Wall time: 1min 5s
The analysis of the database is as follows:
mysql> select count(1) from stock_hfq;
+----------+
| count(1) |
+----------+
| 11546271 |
+----------+
1 row in set (3 min 31.64 sec)
mysql> select count(1) from (select distinct ts_code from stock_hfq b) t;
+----------+
| count(1) |
+----------+
| 4480 |
+----------+
1 row in set (1.26 sec)
mysql> select count(1) from stock_hfq_temp;
+----------+
| count(1) |
+----------+
| 1502 |
+----------+
1 row in set (0.18 sec)
Both tables are indexed.
mysql> show index from stock_hfq;
+-----------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-----------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| stock_hfq | 1 | ts_code | 1 | ts_code | A | 16782 | NULL | NULL | YES | BTREE | | | YES | NULL |
| stock_hfq | 1 | trade_date | 1 | trade_date | A | 94773 | NULL | NULL | YES | BTREE | | | YES | NULL |
+-----------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
2 rows in set (0.00 sec)
mysql> show index from stock_hfq_temp;
+----------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+----------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| stock_hfq_temp | 1 | ts_code | 1 | ts_code | A | 1502 | NULL | NULL | YES | BTREE | | | YES | NULL |
+----------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.00 sec)
explain:
mysql> explain select * from stock_hfq t where exists (select 1 from stock_hfq_temp b where b.ts_code =t.ts_code);
+----+-------------+-------+------------+-------+---------------+---------+---------+----------------------+------+----------+-------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+----------------------+------+----------+-------------------------------------+
| 1 | SIMPLE | b | NULL | index | ts_code | ts_code | 83 | NULL | 1502 | 100.00 | Using where; Using index; LooseScan |
| 1 | SIMPLE | t | NULL | ref | ts_code | ts_code | 83 | quant_test.b.ts_code | 681 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+----------------------+------+----------+-------------------------------------+
2 rows in set, 2 warnings (0.00 sec)
mysql> show warnings;
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note | 1276 | Field or reference 'quant_test.t.ts_code' of SELECT #2 was resolved in SELECT #1 |
| Note | 1003 | /* select#1 */ select `quant_test`.`t`.`ts_code` AS `ts_code`,`quant_test`.`t`.`trade_date` AS `trade_date`,`quant_test`.`t`.`open` AS `open`,`quant_test`.`t`.`high` AS `high`,`quant_test`.`t`.`low` AS `low`,`quant_test`.`t`.`close` AS `close`,`quant_test`.`t`.`pre_close` AS `pre_close`,`quant_test`.`t`.`change` AS `change`,`quant_test`.`t`.`pct_chg` AS `pct_chg`,`quant_test`.`t`.`vol` AS `vol`,`quant_test`.`t`.`amount` AS `amount`,`quant_test`.`t`.`adj_factor` AS `adj_factor` from `quant_test`.`stock_hfq` `t` semi join (`quant_test`.`stock_hfq_temp` `b`) where (`quant_test`.`t`.`ts_code` = `quant_test`.`b`.`ts_code`) |
+-------+------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
2 rows in set (0.00 sec)
CREATE TABLE statements for all relevant tables :
/*
Navicat Premium Data Transfer
Source Server : localhost_3306
Source Server Type : MySQL
Source Server Version : 80021
Source Host : localhost:3306
Source Schema : quant_test
Target Server Type : MySQL
Target Server Version : 80021
File Encoding : 65001
Date: 12/06/2021 13:55:28
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for stock_hfq
-- ----------------------------
DROP TABLE IF EXISTS `stock_hfq`;
CREATE TABLE `stock_hfq` (
`ts_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`trade_date` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`open` double DEFAULT NULL,
`high` double DEFAULT NULL,
`low` double DEFAULT NULL,
`close` double DEFAULT NULL,
`pre_close` double DEFAULT NULL,
`change` double DEFAULT NULL,
`pct_chg` double DEFAULT NULL,
`vol` double DEFAULT NULL,
`amount` double DEFAULT NULL,
`adj_factor` double DEFAULT NULL,
INDEX `ts_code`(`ts_code`) USING BTREE,
INDEX `trade_date`(`trade_date`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
/*
Navicat Premium Data Transfer
Source Server : localhost_3306
Source Server Type : MySQL
Source Server Version : 80021
Source Host : localhost:3306
Source Schema : quant_test
Target Server Type : MySQL
Target Server Version : 80021
File Encoding : 65001
Date: 12/06/2021 13:55:38
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for stock_hfq_temp
-- ----------------------------
DROP TABLE IF EXISTS `stock_hfq_temp`;
CREATE TABLE `stock_hfq_temp` (
`ts_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL,
`name` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
`trade_date` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
`amount` double DEFAULT NULL,
`vol` double DEFAULT NULL,
`close` double DEFAULT NULL,
`h1` double DEFAULT NULL,
`mid` double DEFAULT NULL,
`l1` double DEFAULT NULL,
INDEX `ts_code`(`ts_code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
SELECT
`id`, `code`, `description`, `minamt`
FROM `coupons`
WHERE
`starts`<=DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
`ends`>=DATE_FORMAT(NOW(),"%Y-%m-%d")
and
active=1
and
is_public=1
This mysql took 6 to 7 second to execute , because there are 100k records in coupons table
Table structure
CREATE TABLE IF NOT EXISTS `coupons` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bulk_coupon` int(11) DEFAULT '0',
`ctype` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Type',
`code` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT 'n/a' COMMENT 'Code',
`discount` float(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Discount',
`description` text COLLATE utf8_bin,
`minamt` float(10,2) NOT NULL DEFAULT '0.00' COMMENT 'Min. amount',
`custlogin` tinyint(1) NOT NULL DEFAULT '2' COMMENT 'Requires customer login',
`freeshipping` tinyint(1) NOT NULL DEFAULT '2' COMMENT 'Free shipping',
`customer` text COLLATE utf8_bin,
`products` text COLLATE utf8_bin COMMENT 'Specific products',
`categories` text COLLATE utf8_bin COMMENT 'Spedific categories',
`aod` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'Apply on discounted products',
`starts` date NOT NULL COMMENT 'Start on',
`ends` date NOT NULL COMMENT 'Ends on',
`is_public` tinyint(1) DEFAULT '0',
`active` tinyint(1) DEFAULT '2' COMMENT 'Active',
`usage_type` tinyint(1) DEFAULT '0',
`is_used` tinyint(1) DEFAULT '0',
`cod_applicable` tinyint(1) DEFAULT '0',
`return_policy` tinyint(1) DEFAULT '1',
`added` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `startEndDate` (`starts`,`ends`,`is_public`,`active`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1201682 ;
Simplification:
+------------+-------------------------------+
| CURDATE() | DATE_FORMAT(NOW(),"%Y-%m-%d") |
+------------+-------------------------------+
| 2019-02-19 | 2019-02-19 |
+------------+-------------------------------+
Indexes needed (Optimizer will pick one or the other):
INDEX(active, is_public, start)
INDEX(active, is_public, end)
Don't use FLOAT or DOUBLE for currency. Use DECIMAL.
A query rewrite might be.
Query
SELECT
coupons.id
, coupons.code
, coupons.description
, coupons.minamt
FROM (
SELECT
coupons.id
FROM coupons
WHERE
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
and
coupons.active=1
and
coupons.is_public=1
) AS coupons_start
INNER JOIN
coupons
ON
coupons_start.id = coupons.id
AND
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
coupons.ends >= DATE_FORMAT(NOW(),"%Y-%m-%d")
This one seams to have a "better" exection plan that your query.
Keep in mind executions plans on a empty table are not really sound.
So you need to do the EXPLAINS on your own MySQL also to verify
EXPLAIN
SELECT
`id`, `code`, `description`, `minamt`
FROM `coupons`
WHERE
`starts`<=DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
`ends`>=DATE_FORMAT(NOW(),"%Y-%m-%d")
and
active=1
and
is_public=1
;
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| --- | ----------- | ------- | ----- | ------------- | ------------ | ------- | --- | ---- | ----------- |
| 1 | SIMPLE | coupons | range | startEndDate | startEndDate | 3 | | 1 | Using where |
Notice the key_len which is only 3 which means the query can only use a very small part of the startEndDate key
EXPLAIN
SELECT
coupons.id
, coupons.code
, coupons.description
, coupons.minamt
FROM (
SELECT
coupons.id
FROM coupons
WHERE
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
) AS coupons_start
INNER JOIN
coupons
ON
coupons_start.id = coupons.id
AND
coupons.starts <= DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
coupons.ends >= DATE_FORMAT(NOW(),"%Y-%m-%d")
AND
coupons.active = 1
AND
coupons.is_public = 1
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
| --- | ----------- | ------- | ----- | ------------- | ------------ | ------- | --- | ---- | --------------------------------------------------- |
| 1 | PRIMARY | | | | | | | | Impossible WHERE noticed after reading const tables |
| 2 | DERIVED | coupons | index | startEndDate | startEndDate | 10 | | 1 | Using where; Using index |
Like i really said it's not really sound to get explains on empty tables. Notice the Impossible WHERE noticed after reading const tables the optimizer know here the table is empty.
But also notice that the key_len is 10 and has the index type and using index which means the inner query can get the needed informations the id's to join from the index file alone..
I have two huge tables from which i have select huge amount of data.
Tables store Purchase Order Details and product information.
PURCHASE_ORDER_DETAILS.
CREATE TABLE `PURCHASE_ORDER_DETAILS` (
`PURCHASE_ORDER_NUMBER_PF` INT(20) NOT NULL,
`PRODUCT_CODE_PF` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`ORDER_QUANTITY` INT(8) DEFAULT NULL,
`UNIT_PRICE` DECIMAL(12,2) DEFAULT NULL,
`ORDER_FULLFILLMENT_DUE_DATE` DATETIME DEFAULT NULL,
`DELIVERY_ADDRESS` VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`DELIVERY_CITY` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`DELIVERY_ZIP` BIGINT(10) DEFAULT NULL,
`other columns`
PRIMARY KEY (`PURCHASE_ORDER_NUMBER_PF`,`PRODUCT_CODE_PF`),
KEY `RMAPWBTX_PUCH_ORDE_DLST_INDX` (`DELIVERY_STATE_ID_FK`),
KEY `RMAPWBTX_PUCH_ORDE_DLTY_INDX` (`DELIVERY_TYPE_FK`),
KEY `RMAPWBTX_PUCH_ORDE_TACO_INDX` (`TAX_CODE_FK`),
KEY `RMAPWBMS_PUOR_DETL_PDCO_FK` (`PRODUCT_CODE_PF`),
KEY `RMAPWBTX_PUOR_DETL_TACO_FK` (`TAX_CODE_FK`),
KEY `CREATED_DATE_INDX` (`CREATED_DATE`),
KEY `MODIFIED_DATE_INDX` (`MODIFIED_DATE`),
CONSTRAINT `RMAPWBMS_PUOR_DETL_PDCO_FK` FOREIGN KEY (`PRODUCT_CODE_PF`)
REFERENCES `PRODUCT` (`PRODUCT_CODE_PK`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `RMAPWBMS_PUOR_DETL_PONU_FK` FOREIGN KEY
(`PURCHASE_ORDER_NUMBER_PF`) REFERENCES `PURCHASE_ORDER`
(`PURCHASE_ORDER_NUMBER_PK`),
CONSTRAINT `RMAPWBTX_PO_DETL_DSID_FK` FOREIGN KEY
(`DELIVERY_STATE_ID_FK`) REFERENCES `STATE` (`STATE_ID_PK`),
CONSTRAINT `RMAPWBTX_PUOR_DETL_TACO_FK` FOREIGN KEY (`TAX_CODE_FK`)
REFERENCES `TAX` (`TAX_CODE_PK`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=INNODB DEFAULT CHARSET=latin1;
PRODUCT
CREATE TABLE `PRODUCT` (
`PRODUCT_CODE_PK` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`PRODUCT_DESC` VARCHAR(256) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`FEE_BILL_CODE` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`other columns`
PRIMARY KEY (`PRODUCT_CODE_PK`),
KEY `CREATED_DATE_INDX` (`CREATED_DATE`),
KEY `MODIFIED_DATE_INDX` (`MODIFIED_DATE`),
KEY `PRODUCT_EXCO_FK` (`EXPENSE_CODE_ID_FK`),
KEY `FK_PRODUCT_ENTITY_TYPE` (`ENTITY_TYPE_CODE_FK`),
CONSTRAINT `FK_PRODUCT_ENTITY_TYPE` FOREIGN KEY (`ENTITY_TYPE_CODE_FK`) REFERENCES `ENTITY_TYPE` (`ENTITY_TYPE_CODE_PK`)
) ENGINE=INNODB DEFAULT CHARSET=latin1
Below query is taking ~10min to get ~1M records.
EXPLAIN SELECT * FROM
PURCHASE_ORDER_DETAILS POD
JOIN PRODUCT PRD ON POD.PRODUCT_CODE_PF=PRD.PRODUCT_CODE_PK;
+----+-------------+-------+------+----------------------------+-----------------
-----------+---------+-----------------------------------------------+-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+----------------------------+----------------------------+---------+-----------------------------------------------+-------+-------+
| 1 | SIMPLE | PRD | ALL | PRIMARY | NULL | NULL | NULL | 14283 | NULL |
| 1 | SIMPLE | POD | ref | RMAPWBMS_PUOR_DETL_PDCO_FK | RMAPWBMS_PUOR_DETL_PDCO_FK | 34 | REALREMIT_PROD_ALTISOURCE.PRD.PRODUCT_CODE_PK | 40 | NULL |
+----+-------------+-------+------+----------------------------+----------------------------+---------+-----------------------------------------------+-------+---
Edit1:
Above query was an example, below is the actual query where i am trying to fetch 1M records (main table POD has 22M records).
SELECT `some columns`
FROM `REALREMIT_PPIPFC_MIG`.MIGR_ORDER_DENORM MPO
INNER JOIN PURCHASE_ORDER_DETAILS POD
ON MPO.PURCHASE_ORDER_NUMBER_PK=POD.PURCHASE_ORDER_NUMBER_PF
INNER JOIN PRODUCT PRD
ON POD.PRODUCT_CODE_PF=PRD.PRODUCT_CODE_PK
INNER JOIN EXPENSE_CODE EXP
ON PRD.EXPENSE_CODE_ID_FK=EXP.EXPENSE_CODE_ID_PK
WHERE MPO.BATCH_ID=1;
Explain Output for the above query
+----+-------------+-------+--------+-------------------------------------+----------------------------+---------+--------------------------------------------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------+----------------------------+---------+--------------------------------------------------------+-------+-------------+
| 1 | SIMPLE | PRD | ALL | PRIMARY,PRODUCT_EXCO_FK | NULL | NULL | NULL | 14283 | NULL |
| 1 | SIMPLE | EXP | eq_ref | PRIMARY | PRIMARY | 4 | REALREMIT_PROD_ALTISOURCE.PRD.EXPENSE_CODE_ID_FK | 1 | NULL |
| 1 | SIMPLE | POD | ref | PRIMARY,RMAPWBMS_PUOR_DETL_PDCO_FK | RMAPWBMS_PUOR_DETL_PDCO_FK | 34 | REALREMIT_PROD_ALTISOURCE.PRD.PRODUCT_CODE_PK | 40 | NULL |
| 1 | SIMPLE | MPO | ref | MIGR_PO_NBR_INDX,MIGR_BATCH_ID_INDX | MIGR_PO_NBR_INDX | 4 | REALREMIT_PROD_ALTISOURCE.POD.PURCHASE_ORDER_NUMBER_PF | 1 | Using where |
+----+-------------+-------+--------+-------------------------------------+----------------------------+---------+--------------------------------------------------------+-------+-------------+
4 rows in set (0.20 sec)
Both database have same charset
Columns used in join have the same collate
Both tables have same charset
I have created new table in which primary key is concatenated column - PURHCASE_ORDER_NUMBER_PF and PRODUCT_CODE_PF and then i have added a new index on PRODUCT_CODE_PF
Will index be used in this case and/or is this the best way to make use of index in join.
Thanks
These may help:
MPO: INDEX(BATCH_ID, PURCHASE_ORDER_NUMBER_PK) -- in this order
EXP: INDEX(EXPENSE_CODE_ID_PK) -- unless it is the PRIMARY KEY
But, without knowing what is in "some columns", I can't predict how much they will help. And it would help to have SHOW CREATE TABLE for MPO and EXP.
How much RAM do you have? What is the value of innodb_buffer_pool_size? I ask because you may be thrashing.
I do not understand the difference (line 2) of those two EXPLAINs. Maybe someone has a hint for me why mysql acts so different on those, which heavily affects query speed.
The slow query lasts 12 seconds (which equals querying all rows with that query) and uses a join on integer columns while the joined table has just 3 records:
SELECT `inv_assets`.`id` AS `id`, `site`.`description` AS `sitename`,
(SELECT COALESCE(DATE_FORMAT(CONVERT_TZ(MIN(inspdate),'UTC','Europe/Vienna'),'%Y-%m-%d'),'')
FROM `mobuto_inv_inspections` AS `nextinsp`
WHERE ((`nextinsp`.`objectlink` = `inv_assets`.`id`
AND `nextinsp`.`inspdate` >= NOW()))
) AS `nextinsp`
FROM `mobuto_inv_assets` AS `inv_assets`
LEFT JOIN `mobuto_inv_sites` AS `site`
ON (`site`.`siteid` = `inv_assets`.`site`
AND `site`.`_state` IN (2,0))
ORDER BY `inv_assets`.`type` ASC LIMIT 0, 20;
+----+--------------------+------------+--------+----------------+---------+---------+------------------------------+-------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+--------+----------------+---------+---------+------------------------------+-------+----------------------------------------------------+
| 1 | PRIMARY | inv_assets | ALL | NULL | NULL | NULL | NULL | 24857 | Using temporary; Using filesort |
| 1 | PRIMARY | site | ALL | PRIMARY,_state | NULL | NULL | NULL | 3 | Using where; Using join buffer (Block Nested Loop) |
| 2 | DEPENDENT SUBQUERY | nextinsp | ALL | inspdate | NULL | NULL | NULL | 915 | Using where |
+----+--------------------+------------+--------+----------------+---------+---------+------------------------------+-------+----------------------------------------------------+
The fast query consumes just a few fractions of a second, uses a join on varchar(32) columns and the joined table has 1352 records:
SELECT `inv_assets`.`id` AS `id`, `guarantor`.`lastname` AS `guarantoruname`,
(SELECT COALESCE(DATE_FORMAT(CONVERT_TZ(MIN(inspdate),'UTC','Europe/Vienna'),'%Y-%m-%d'),'')
FROM `mobuto_inv_inspections` AS `nextinsp`
LEFT JOIN `users` AS `saveuser`
ON (`saveuser`.`uid` = `nextinsp`.`saveuser`
AND `saveuser`.`_state` = '0')
WHERE ((`nextinsp`.`objectlink` = `inv_assets`.`id`
AND `nextinsp`.`inspdate` >= NOW()))
) AS `nextinsp`
FROM `mobuto_inv_assets` AS `inv_assets`
LEFT JOIN `users` AS `guarantor`
ON (`guarantor`.`uid` = `inv_assets`.`guarantor`
AND `guarantor`.`_state` = '0')
ORDER BY `inv_assets`.`type` ASC LIMIT 0, 20;
+----+--------------------+------------+--------+----------------+---------+---------+---------------------------------+-------+----------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+--------------------+------------+--------+----------------+---------+---------+---------------------------------+-------+----------------+
| 1 | PRIMARY | inv_assets | ALL | NULL | NULL | NULL | NULL | 24857 | Using filesort |
| 1 | PRIMARY | guarantor | eq_ref | PRIMARY,_state | PRIMARY | 98 | mobuto_dev.inv_assets.guarantor | 1 | Using where |
| 2 | DEPENDENT SUBQUERY | nextinsp | ALL | inspdate | NULL | NULL | NULL | 915 | Using where |
| 2 | DEPENDENT SUBQUERY | saveuser | eq_ref | PRIMARY,_state | PRIMARY | 98 | mobuto_dev.nextinsp.saveuser | 1 | Using where |
+----+--------------------+------------+--------+----------------+---------+---------+---------------------------------+-------+----------------+
The strange thing to me is, when I remove the column (description) of the joined table in the 'column-select-part' (while the join still persists and IMHO mysql does not optimize it away when not used), the speed is back (because mysql does not use a temporary table any longer and the explain looks same as the fast one, having type=eq_ref).
But why does this work for the first sample only when no column selected, whereas I can select one in the second one!?
CREATE TABLE `mobuto_inv_assets` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`invnum` varchar(10) NOT NULL,
`oebglcat` varchar(4) NOT NULL,
`mark` varchar(100) NOT NULL,
`type` varchar(100) NOT NULL,
`serialnum` varchar(100) NOT NULL,
`desc` varchar(100) NOT NULL,
`site` int(11) NOT NULL DEFAULT '0',
`licnum` varchar(20) NOT NULL DEFAULT '',
`inquirer` varchar(100) NOT NULL DEFAULT '',
`inqdate` date NOT NULL DEFAULT '0000-00-00',
`supplier` varchar(100) NOT NULL DEFAULT '',
`suppldate` date NOT NULL DEFAULT '0000-00-00',
`supplnumber` varchar(30) NOT NULL DEFAULT '',
`invoicedate` date NOT NULL DEFAULT '0000-00-00',
`invoicenumber` varchar(30) NOT NULL DEFAULT '',
`purchaseprice` decimal(11,2) NOT NULL DEFAULT '0.00',
`leased` varchar(1) NOT NULL DEFAULT 'N',
`leasingcompany` varchar(100) NOT NULL DEFAULT '',
`leasingnumber` varchar(30) NOT NULL DEFAULT '',
`notes` text NOT NULL,
`inspnotes` text NOT NULL,
`inactive` varchar(1) NOT NULL DEFAULT 'N',
`maintain` varchar(1) NOT NULL DEFAULT 'Y',
`asset` varchar(1) NOT NULL DEFAULT 'Y',
`inspection` varchar(1) NOT NULL DEFAULT '',
`inspperson` varchar(100) NOT NULL DEFAULT '',
`guarantor` varchar(32) NOT NULL DEFAULT '',
`saveuser` varchar(32) NOT NULL,
`savetime` int(11) NOT NULL,
`recordid` varchar(32) NOT NULL,
`_state` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `invnum` (`invnum`),
KEY `_state` (`_state`),
KEY `site` (`site`)
) ENGINE=InnoDB AUTO_INCREMENT=30707 DEFAULT CHARSET=utf8;
CREATE TABLE `mobuto_inv_sites` (
`siteid` int(11) NOT NULL AUTO_INCREMENT,
`description` varchar(100) NOT NULL,
`saveuser` varchar(32) NOT NULL,
`savetime` int(11) NOT NULL,
`recordid` varchar(32) NOT NULL,
`_state` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`siteid`),
KEY `_state` (`_state`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
mysql> SHOW INDEX FROM mobuto_inv_assets;
+-------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| mobuto_inv_assets | 0 | PRIMARY | 1 | id | A | 24857 | NULL | NULL | | BTREE | | |
| mobuto_inv_assets | 0 | invnum | 1 | invnum | A | 24857 | NULL | NULL | | BTREE | | |
| mobuto_inv_assets | 1 | _state | 1 | _state | A | 4 | NULL | NULL | | BTREE | | |
+-------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
Changes as requested by #Wilson Hauck:
Added index to column site in mobuto_inv_assets (reduced execution speed by almost half a second)
Seems that column nextinsp was missing in first query. Maybe lost while formatting the query. Of course there should be the same as in the fast one
Removed the saveuser join as it is not used there (saved another 2 seconds) and updated its EXPLAIN (last line removed)
SHOW INDEX FROM mobuto_inv_sites added
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| mobuto_inv_sites | 0 | PRIMARY | 1 | siteid | A | 3 | NULL | NULL | | BTREE | | |
| mobuto_inv_sites | 1 | _state | 1 | _state | A | 3 | NULL | NULL | | BTREE | | |
+------------------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
Your first query is making less use of keys than the second. The possible_keys column in the explain plan shows where keys are available to be used, however, the key column shows where they are actually being used.
I would advise, short of seeing the structure of your DB, to make more use of these keys in your JOIN and WHERE clauses to speed it up.
I'd make sure that the query isn't being cached when you say you're modifying the select columns and the speed is varying.
12 seconds first query likely caused by ROWS column clues of simply 24857*3*915*1 = 68,232,465 total rows considered. Less than 1 second for second query ROWS column clues of simply 24857*1*915*1 = 22,744,155 total rows considered. The first query's use of Block Nested Loop processing is a major contributor to delaying the response.
Please post results of SHOW CREATE TABLE mobuto_inv_assets and mobuto_inv_sites. Please also post results of SHOW INDEX FROM mobuto_inv_assets and mobuto_inv_sites. With this additional information someone may be able to suggest improvements to SELECT .... queries that will avoid Block Nested Loop processing that is very time CPU intense with RBAR (Row By Agonizing Row processing). Additional indexing may be required.
Thanks for posting your two SHOW CREATE TABLE's, immensely helpful.
Please consider adding index with
ALTER TABLE mobuto_inv_sites ADD INDEX site --
if space permits on your system.
Also, the EXPLAIN showing for query1 is mismatched to the query.
The query does not refer to nextinsp or saveused that I can see in EXPLAIN.
Please replace the EXPLAIN for query1 after creating the index when you have an opportunity to test again and indicate any reduction in execution time required.
It would also be nice if you could post results of
SHOW INDEX FROM mobuto_inv_sites so we can see the scope of your data and cardinality.
If the inv_assets rows are populated with ACCURATE _state data
consider changing query1 to something like the following:
SELECT inv_assets.id AS id, site.description AS sitename,
(SELECT COALESCE(DATE_FORMAT(CONVERT_TZ(MIN(inspdate),'UTC','Europe/Vienna'),'%Y-%m-%d'),'')
FROM mobuto_inv_inspections AS nextinsp
WHERE ((nextinsp.objectlink = inv_assets.id
AND nextinsp.inspdate >= NOW()))
) AS nextinsp
FROM mobuto_inv_assets AS inv_assets
WHERE inv_assets._state = 2 OR inv_assets._state = 0
LEFT JOIN mobuto_inv_sites AS site
ON (site.siteid = inv_assets.site
AND site._state IN (2,0))
ORDER BY inv_assets.type ASC LIMIT 0, 20;
EXPLAIN should avoid table scan and subsequent Block Nested Loop processing.
If _state data in inv_assets is not ACCURATE on every row, this will not work.
2017-08-10 update 09:42 CT please post QUERY, EXPLAIN result, SHOW CREATE TABLE tblname for tables involved and SHOW INDEX FROM tblname for tables involved.
For some reason, when I do an update on a particular table in MySQL, the response is:
"Rows matched: 1 Changed: 0 Warnings: 0"
I can not for the life of me work out why. I can do a select from the database: (I have X'd out the sensitive data - it's just text)
mysql> SELECT * FROM outgoings WHERE id=198;
+-----+---------+---------------+------+-----+----------------+-----------------+-----------+------------------------------------+-----------+------------+------------------+---------------------+---------------------+---------------+---------------+-----------+-----------------+---------------------------------+-------------------+---------------------+----------------+
| id | user_id | outgoing_date | form | bsb | account_number | transfer_number | amount | recipient | client_id | project_id | purpose | created_at | updated_at | received_from | cheque_number | cheque_to | project_id_from | cheque_drawer | recipient_purpose | transferrer_purpose | client_id_from |
+-----+---------+---------------+------+-----+----------------+-----------------+-----------+------------------------------------+-----------+------------+------------------+---------------------+---------------------+---------------+---------------+-----------+-----------------+---------------------------------+-------------------+---------------------+----------------+
| 198 | 2 | 2015-03-11 | 3 | | | | 407481.25 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | 162 | 503 | XXXXXXXXXXXXXXXX | 2015-03-13 17:51:36 | 2015-03-13 17:51:36 | | 161 | | NULL | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | | | NULL |
+-----+---------+---------------+------+-----+----------------+-----------------+-----------+------------------------------------+-----------+------------+------------------+---------------------+---------------------+---------------+---------------+-----------+-----------------+---------------------------------+-------------------+---------------------+----------------+
1 row in set (0.00 sec)
As you can see, the amount column is 407481.25. I then run a simple update query:
mysql> UPDATE cl_time.outgoings SET amount=407481.24 WHERE outgoings.id=198;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
The result shows that nothing was updated, despite the fact that I am actually trying to change something. And if I run the select query again, I see the same result:
mysql> SELECT * FROM outgoings WHERE id=198;
+-----+---------+---------------+------+-----+----------------+-----------------+-----------+------------------------------------+-----------+------------+------------------+---------------------+---------------------+---------------+---------------+-----------+-----------------+---------------------------------+-------------------+---------------------+----------------+
| id | user_id | outgoing_date | form | bsb | account_number | transfer_number | amount | recipient | client_id | project_id | purpose | created_at | updated_at | received_from | cheque_number | cheque_to | project_id_from | cheque_drawer | recipient_purpose | transferrer_purpose | client_id_from |
+-----+---------+---------------+------+-----+----------------+-----------------+-----------+------------------------------------+-----------+------------+------------------+---------------------+---------------------+---------------+---------------+-----------+-----------------+---------------------------------+-------------------+---------------------+----------------+
| 198 | 2 | 2015-03-11 | 3 | | | | 407481.25 | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | 162 | 503 | XXXXXXXXXXXXXXXX | 2015-03-13 17:51:36 | 2015-03-13 17:51:36 | | 161 | | NULL | XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX | | | NULL |
+-----+---------+---------------+------+-----+----------------+-----------------+-----------+------------------------------------+-----------+------------+------------------+---------------------+---------------------+---------------+---------------+-----------+-----------------+---------------------------------+-------------------+---------------------+----------------+
1 row in set (0.00 sec)
For a bit more context, this is the table structure:
CREATE TABLE `outgoings` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`outgoing_date` date NOT NULL,
`form` int(11) NOT NULL,
`bsb` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`account_number` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`transfer_number` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`amount` float(8,2) NOT NULL,
`recipient` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`client_id` int(11) NOT NULL,
`project_id` int(11) NOT NULL,
`purpose` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`received_from` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`cheque_number` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`cheque_to` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`project_id_from` int(11) DEFAULT NULL,
`cheque_drawer` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`recipient_purpose` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`transferrer_purpose` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`client_id_from` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `outgoings_user_id_index` (`user_id`),
KEY `outgoings_client_id_index` (`client_id`),
KEY `outgoings_project_id_index` (`project_id`),
KEY `outgoings_project_id_from_index` (`project_id_from`),
KEY `outgoings_client_id_from_index` (`client_id_from`)
) ENGINE=MyISAM AUTO_INCREMENT=228 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
And finally, this table has data inserted and written to it by a PHP application that I wrote, in Laravel. However, the app never runs an update query on this table. All of this used to work fine, until I migrated the app to a new server, and I'm now unable to run my updates.
Oh, any my mysql version is: 5.5.41, running on Ubuntu.
(These questions seem to be asking much the same, however none of the answers there work for me, so I'm asking here with all of my data: Why would rows match but not be changed with a mysql update statement? and Update statement in mysql not working, while it has to work)
The reason an update doesn't change the value of your row is because of rounding issues. You have defined amount as float(8,2). You are then changing the value from 407481.25 to 407481.24 - the difference is mere 0.01 between them but we know that computers have rounding issues.
Due to the fact that an update doesn't write the new value, it's 100% safe to conclude that MySQL sees the two numbers as the same due to rounding issues. If a record isn't really altered, MySQL won't write it down - this is an optimization step, MySQL won't involve hard drive into work if it doesn't have to.
Now, this means you need to adjust your amount column type and change it to either an integer and then internally move the decimal point for two places to the left or use a data type such as decimal with larger decimal digits maximum number (for example: amount DECIMAL(8,4)).
In mysql updates, when the values in the row don't actually change, it doesn't count toward the changed row count (obviously).
"Changed" means "the new values are different from the old values".
you can change the data type to DOUBLE :
alter table outgoings modify amount DOUBLE;