Skip to content

10 | MySQL为什么有时候会选错索引? #19

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
git-zjx opened this issue Jul 24, 2019 · 3 comments
Open

10 | MySQL为什么有时候会选错索引? #19

git-zjx opened this issue Jul 24, 2019 · 3 comments
Assignees
Labels
MySQL MySQL MySQL实战45讲 MySQL实战45讲笔记

Comments

@git-zjx
Copy link
Owner

git-zjx commented Jul 24, 2019

优化器的逻辑

优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。
在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。
当然扫描行数并不是唯一的判断标准,优化器还会结合是否使用临时表、是否排序等因素进行综合判断。

扫描行数的获取方式

MySQL 在真正开始执行语句之前,并不能精确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。
这个统计信息就是索引的“区分度”。显然,一个索引上不同的值越多,这个索引的区分度就越好。而一个索引上不同的值的个数,我们称之为“基数”(cardinality)。也就是说,这个基数越大,索引的区分度越好。
可以使用 show index 方法,看到一个索引的基数

获得索引基数的方式

使用采样统计的方法,因为把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太高了

采样统计的时候,InnoDB 默认会选择 N 个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。
而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 1/M 的时候,会自动触发重新做一次索引统计。
在 MySQL 中,有两种存储索引统计的方式,可以通过设置参数 innodb_stats_persistent 的值来选择:

  • 设置为 on 的时候,表示统计信息会持久化存储。这时,默认的 N 是 20,M 是 10。
  • 设置为 off 的时候,表示统计信息只存储在内存中。这时,默认的 N 是 8,M 是 16。

analyze table t 命令,可以用来重新统计索引信息

索引选择异常和处理

  1. 采用 force index 强行选择一个索引
  2. 修改语句,引导 MySQL 使用我们期望的索引
  3. 新建一个更合适的索引,来提供给优化器做选择,或删掉误用的索引
@git-zjx git-zjx added MySQL MySQL MySQL实战45讲 MySQL实战45讲笔记 labels Jul 24, 2019
@git-zjx git-zjx self-assigned this Mar 2, 2020
@git-zjx
Copy link
Owner Author

git-zjx commented Mar 2, 2020

merge 的过程是否会把数据直接写回磁盘?

merge 的执行流程是这样的:

  • 从磁盘读入数据页到内存(老版本的数据页);
  • 从 change buffer 里找出这个数据页的 change buffer 记录 (可能有多个),依次应用,得到新版数据页;
  • 写 redo log。这个 redo log 包含了数据的变更和 change buffer 的变更。
    到这里 merge 过程就结束了。这时候,数据页和内存中 change buffer 对应的磁盘位置都还没有修改,属于脏页,之后各自刷回自己的物理数据,就是另外一个过程了。

@git-zjx
Copy link
Owner Author

git-zjx commented Mar 2, 2020

假如要查 A in () AND B in (), 怎么建索引?

where A in (a,b,c) AND B in (x,y,z) 会转成 (A=a and B=x) or (A=a and B=y) or (A=a and B=z) or (A=b and B=x) or (A=b and B=y) or (A=b and B=z) or (A=c and B=x) or (A=c and B=y) or (A=c and B=z)

@git-zjx
Copy link
Owner Author

git-zjx commented Mar 2, 2020

删除索引的时候能做到秒删,这个什么原理呢?

标记删除

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
MySQL MySQL MySQL实战45讲 MySQL实战45讲笔记
Projects
None yet
Development

No branches or pull requests

1 participant