【问题标题】:why select different column can affect query speed in mysql 5.6?为什么选择不同的列会影响 mysql 5.6 中的查询速度?
【发布时间】:2019-05-08 19:48:48
【问题描述】:

我在mysql5.6中有一张cdc_bond_valuation表:

+--------------+--------------------+--------------+---------------------------------+--------------+--------------+-------------+
| table_schema | table_name         | index_schema | index_name                      | seq_in_index | column_name  | cardinality |
+--------------+--------------------+--------------+---------------------------------+--------------+--------------+-------------+
| ss_product   | cdc_bond_valuation | ss_product   | IDX_cdc_bond_valuation_Bond_Key |            1 | Bond_Key     |      377844 |
| ss_product   | cdc_bond_valuation | ss_product   | IndexValuateDate                |            1 | Valuate_Date |      143025 |
| ss_product   | cdc_bond_valuation | ss_product   | PRIMARY                         |            1 | ID           |    25315548 |
+--------------+--------------------+--------------+---------------------------------+--------------+--------------+-------------+



query 1:
SELECT  Valuate_Date  FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;

query 2:
SELECT ID, Valuate_Date  FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;

query 3:
SELECT  Bond_Key FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;

query 4:
SELECT  Bond_Key,Valuate_Date FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;


explain 1:
mysql> explain SELECT  Valuate_Date  FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------------+
| id | select_type | table              | type | possible_keys    | key              | key_len | ref   | rows  | Extra       |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | cdc_bond_valuation | ref  | IndexValuateDate | IndexValuateDate | 5       | const | 98156 | Using index |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------------+
1 row in set

explain 2:
mysql> explain SELECT  ID,Valuate_Date FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------------+
| id | select_type | table              | type | possible_keys    | key              | key_len | ref   | rows  | Extra       |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | cdc_bond_valuation | ref  | IndexValuateDate | IndexValuateDate | 5       | const | 98156 | Using index |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------------+
1 row in set


explain 3:
mysql> explain SELECT  Bond_Key FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------+
| id | select_type | table              | type | possible_keys    | key              | key_len | ref   | rows  | Extra |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------+
|  1 | SIMPLE      | cdc_bond_valuation | ref  | IndexValuateDate | IndexValuateDate | 5       | const | 98156 | NULL  |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------+
1 row in set

explain 4:
mysql> explain SELECT  Bond_Key,Valuate_Date FROM cdc_bond_valuation WHERE Valuate_Date = 20181203;
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------+
| id | select_type | table              | type | possible_keys    | key              | key_len | ref   | rows  | Extra |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------+
|  1 | SIMPLE      | cdc_bond_valuation | ref  | IndexValuateDate | IndexValuateDate | 5       | const | 98156 | NULL  |
+----+-------------+--------------------+------+------------------+------------------+---------+-------+-------+-------+
1 row in set



mysql> select table_schema,
       table_name,
       index_schema,
       index_name,
       seq_in_index,
       column_name,
       cardinality
  from information_schema.statistics
 where table_name = 'cdc_bond_valuation'
 order by table_schema, table_name, index_name, seq_in_index;

+--------------+--------------------+--------------+---------------------------------+--------------+--------------+-------------+
| table_schema | table_name         | index_schema | index_name                      | seq_in_index | column_name  | cardinality |
+--------------+--------------------+--------------+---------------------------------+--------------+--------------+-------------+
| ss_product   | cdc_bond_valuation | ss_product   | IDX_cdc_bond_valuation_Bond_Key |            1 | Bond_Key     |      377844 |
| ss_product   | cdc_bond_valuation | ss_product   | IndexValuateDate                |            1 | Valuate_Date |      143025 |
| ss_product   | cdc_bond_valuation | ss_product   | PRIMARY                         |            1 | ID           |    25315548 |
+--------------+--------------------+--------------+---------------------------------+--------------+--------------+-------------+
3 rows in set

mysql> 

如上表有4个查询,它们都使用索引IndexValuateDate,但是查询1和2非常快(不到1秒),但查询3和4非常慢(超过1000秒)。

我注意到 1 和 2 只是使用索引来反馈查询(ID 是主键,Valuate_Date 是索引)。 3 和 4 首先使用 Value_Date 上的索引来过滤表,然后返回表获取具有 rowid 的列?为什么不直接使用像 1 和 2 这样的索引,因为 Bond_Key 也被索引了?

【问题讨论】:

  • 列类型:Value_Date decimal(8,0)
  • 查询优化器必须使用统计和启发式方法来确定是否应该使用索引。您是否尝试过使用 OPTIMIZE TABLE 更新这些统计信息?
  • 还没有。 OPTIMIZE 很贵,我们还没试过。
  • @EnricoDias - 对于 InnoDB,ANALYZE TABLE 会更新统计信息。但是,OPTIMIZEANALYZE 很少需要。如果在这种情况下有帮助,那只是巧合。
  • 我认为WHERE 之后的 SQL 匹配列,如表列的顺序是 id、name、date。使用* 查询将比id, name, date 更快,因为SQL 将从表中获取值,然后将它们与SELECT 中的顺序匹配,类似地,当WHERESELECT 中的列相同时,即date 查询采取较少时间,因为它的值已经被获取,然后首先获取日期匹配的值,然后获取name 的值。我不是 SQL 开发人员,只是我的解释。

标签: mysql performance indexing explain


【解决方案1】:
SELECT  count(*)  FROM cdc_bond_valuation WHERE   bond_key='C0000832017CORLEB01';
SELECT  count(*)  FROM cdc_bond_valuation WHERE   bond_key='C0000832017CORLEB01' and Valuate_Date = 20181203;


+----------+
| count(*) |
+----------+
|      788 |
+----------+
1 row in set

+----------+
| count(*) |
+----------+
|        2 |
+----------+
1 row in set

mysql> 

【讨论】:

  • 对于INDEX(bond_key),在使用WHERE 子句中的任何一个时都必须查看788 行。使用INDEX(bond_key, Value_Date)(以任意顺序),第二个WHERE 子句将只查看(索引的)2 行,--因此要快得多。对于 2 列索引和第一个 WHERE 的组合:查看了 788 行。因此,如果这些是您使用的唯一 WHEREs,则 2-col 索引既是最佳的,又是使用最少数量的 INDEXes
【解决方案2】:

查询 5:

mysql> explain SELECT Bond_Key, Valuate_Date  FROM cdc_bond_valuation WHERE Valuate_Date = 20181203  and bond_key='C0000832017CORLEB01';
+----+-------------+--------------------+------+--------------------------------------------------+---------------------------------+---------+-------+------+------------------------------------+
| id | select_type | table              | type | possible_keys                                    | key                             | key_len | ref   | rows | Extra                              |
+----+-------------+--------------------+------+--------------------------------------------------+---------------------------------+---------+-------+------+------------------------------------+
|  1 | SIMPLE      | cdc_bond_valuation | ref  | IndexValuateDate,IDX_cdc_bond_valuation_Bond_Key | IDX_cdc_bond_valuation_Bond_Key | 78      | const |  787 | Using index condition; Using where |
+----+-------------+--------------------+------+--------------------------------------------------+---------------------------------+---------+-------+------+------------------------------------+
1 row in set

mysql> 

查询 5 使用 index(bond_key) 进行过滤,然后像全表扫描一样扫描结果(787 行)以查找 bond_key 。查询 5 根本不使用 index(ValuateDate)。对吗?

【讨论】:

  • 在不知道IDX_cdc_bond_valuation_Bond_Key 中有哪些列的情况下,我们无法对此查询及其EXPLAIN 发表评论。如果是justBond_Key,那么它必须在索引和数据之间反弹大约 787 次。 787 是使用该 bond_key 的行数(在索引中)的估计值;它只需要查看索引中的那 787 行,以及数据中的那么多行(以获取日期)。它会比INDEX(Bond_key, ValuateDate)INDEX(ValuateDate, Bond_key)
  • 谢谢瑞克。 IDX_cdc_bond_valuation_Bond_Key 是索引(Bond_key)。至于查询 5,我仍然对索引(Bond_key)中返回 787 行后的处理感到困惑。据我了解,优化器只会将 787 行作为一个表,并在“787 行表”中进行全扫描以获取 ValuateDate 的数据。
  • 好的,“术语”问题... 正在扫描的索引的 787 行子集称为“范围扫描”。 “扫描”意味着查看连续项目。 “范围”意味着表或索引的子集。同时,这些令人分心:“完整”实现所有表或索引。 “表”指的是所有行。
【解决方案3】:
    CREATE TABLE `cdc_bond_valuation` (
  `ID` varchar(32) NOT NULL,
  `Bond_Key` varchar(25) DEFAULT NULL,
  `Short_Name` varchar(32) DEFAULT NULL,
  `Bond_ID` varchar(32) DEFAULT NULL,
  `Valuate_Date` decimal(8,0) DEFAULT NULL,
  `Listed_Market` varchar(3) DEFAULT NULL,
  `Remaining_Year` decimal(7,4) DEFAULT NULL,
  `Val_Intraday_Dirty_Price` decimal(7,4) DEFAULT NULL,
  `Val_Intraday_Accrued_Interest` decimal(7,4) DEFAULT NULL,
  `Val_Clean_Price` decimal(7,4) DEFAULT NULL,
  `Val_Yield` decimal(7,4) DEFAULT NULL,
  `Val_Modified_Duration` decimal(7,4) DEFAULT NULL,
  `Val_Convexity` decimal(7,4) DEFAULT NULL,
  `Val_Basis_Point_Value` decimal(7,4) DEFAULT NULL,
  `Val_Spread_Duration` decimal(7,4) DEFAULT NULL,
  `Val_Spread_Convexity` decimal(7,4) DEFAULT NULL,
  `Market_Dirty_Price` decimal(7,4) DEFAULT NULL,
  `Market_Clean_Price` decimal(7,4) DEFAULT NULL,
  `Market_Yield` decimal(7,4) DEFAULT NULL,
  `Market_Modified_Duration` decimal(7,4) DEFAULT NULL,
  `Market_Convexity` decimal(7,4) DEFAULT NULL,
  `Market_Basis_Point_Value` decimal(7,4) DEFAULT NULL,
  `Market_Spread_Duration` decimal(7,4) DEFAULT NULL,
  `Market_Spread_Convexity` decimal(7,4) DEFAULT NULL,
  `Credibility` varchar(16) DEFAULT NULL,
  `Val_Rate_Duration` decimal(7,4) DEFAULT NULL,
  `Val_Rate_Convexity` decimal(7,4) DEFAULT NULL,
  `Market_Rate_Duration` decimal(7,4) DEFAULT NULL,
  `Market_Rate_Convexity` decimal(7,4) DEFAULT NULL,
  `Val_Closed_Dirty_Price` decimal(7,4) DEFAULT NULL,
  `Val_Closed_Accrued_Interest` decimal(7,4) DEFAULT NULL,
  `Remaining_Par_Value` decimal(7,4) DEFAULT NULL,
  `Val_Spread` decimal(7,4) DEFAULT NULL,
  `Yield_Curve_ID` varchar(128) DEFAULT NULL,
  `Market_Spread` decimal(7,4) DEFAULT NULL,
  `Absolute_Liquidity_Coefficient` decimal(7,4) DEFAULT NULL,
  `Position_Percentage` decimal(7,4) DEFAULT NULL,
  `Relative_Liquidity_Coefficient` decimal(7,4) DEFAULT NULL,
  `Relative_Liquidity_Value` decimal(7,4) DEFAULT NULL,
  `Option` varchar(8) DEFAULT NULL ,
  PRIMARY KEY (`ID`),
  KEY `IndexValuateDate` (`Valuate_Date`) USING BTREE,
  KEY `ValuateDateBondKey` (`Valuate_Date`,`Bond_Key`),
  KEY `IndexBondKey` (`Bond_Key`,`Listed_Market`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8

【讨论】:

  • KEY IndexValuateDate 是多余的,可以是DROPped
  • 在我们的系统中,有很多表有 3-4 个组件索引多次覆盖同一列。我假设优化器会被这些索引弄糊涂,最后选择的索引可能不正确。我更喜欢单列索引,因为它很简单,我知道在某些情况下单索引效率不如组件索引。
【解决方案4】:

请提供SHOW CREATE TABLE

InnoDB 以静默方式将PRIMARY KEY 的列添加到每个辅助键。因此查询 1 和 2 执行相同。他们只使用索引。这在EXPLAIN 中由Using index 表示。即INDEX(Valuate_Date)包含需要的列,其他列不需要。

EXPLAINs 表示使用了相同的索引,但它不是“覆盖”(没有提及Using index)。所以索引是线性扫描的,但是对于每个估计的具有该日期的 98156 个条目,它必须查找(在数据的 BTree 中)Bond_Key 的值。这种额外的查找导致了严重的减速。 (1000 秒非常适合在 HDD 上进行 98156 次磁盘访问。)

要使所有 4 个查询都快速,请将 IndexValuateDate 替换为此复合索引,并将列按给定的顺序排列:

INDEX(Valuate_Date, Bond_Key, ID)

我建议您通过DATE 数据类型而不是DECIMAL(8,0) 处理日期。

与其他数据库不同,MySQL 没有“rowid”。相反,PRIMARY KEY 在 BTree 中用于对数据进行排序。

【讨论】:

  • 感谢瑞克的解释。对于查询 3 和 4,我可以理解它们都使用 Valuate_Date 的索引进行过滤,对于结果(98156),然后像全表扫描一样扫描 98156 行以获得 Bond_Key
  • @lee - 是的,他们将索引 BTree 视为一个表,并从具有 Valuate_Date = 20181203 的第一行到具有该值的最后一行进行“范围”扫描(估计为 98K 行)。在此期间,他们将bond_key 发送给查询 3 和 4 中的用户。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-05
  • 2014-03-21
  • 2015-12-20
  • 2015-05-31
  • 1970-01-01
相关资源
最近更新 更多