惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
PCI Perspectives
PCI Perspectives
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Heimdal Security Blog
S
Security @ Cisco Blogs
N
News | PayPal Newsroom
J
Java Code Geeks
罗磊的独立博客
Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
V
V2EX
WordPress大学
WordPress大学
Google Online Security Blog
Google Online Security Blog
N
News and Events Feed by Topic
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
AI
AI
小众软件
小众软件
The GitHub Blog
The GitHub Blog
MongoDB | Blog
MongoDB | Blog
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
美团技术团队
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
S
Schneier on Security
博客园 - 三生石上(FineUI控件)
F
Full Disclosure
B
Blog RSS Feed
Forbes - Security
Forbes - Security
S
SegmentFault 最新的问题
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
人人都是产品经理
人人都是产品经理
云风的 BLOG
云风的 BLOG
Jina AI
Jina AI
Cisco Talos Blog
Cisco Talos Blog
U
Unit 42
Project Zero
Project Zero
H
Hacker News: Front Page
Y
Y Combinator Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Cloudflare Blog
大猫的无限游戏
大猫的无限游戏
S
Secure Thoughts
The Hacker News
The Hacker News
Microsoft Azure Blog
Microsoft Azure Blog

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例) 读《为什么学生不喜欢上学》 OpenAI Prompt Engineering 摘录和总结 读《打造真正的新产品》 VueJS 总结 Linux 自动挂载 alist 提供的webdav FreeBSD 使用 vm-bhyve 安装Debian虚拟机 FreeBSD 和 Linux 网卡聚合实现提速 GPT 帮我搞定了时区转换问题 长任务系统如何处理? macOS/Linux 编译 InputLeap 使用开源软KVM - synergy-core 解决 macOS 终端hostname一直变化问题 KVM 共享 Intel 集成显卡 PromQL 备忘 读《格鲁夫给经理人的第一课》 读《打开心智》 为什么要把复杂的联表操作拆成多个单表查询? 红包系统的设计 MySQL Index Condition Pushdown Optimization Go mod 简明教程 OpenWRT 使用 Android/iOS USB 网络 搭建旁路由 Golang gRPC 错误处理 编写可维护的单元测试代码 OAuth 2 详解(六):Authorization Code Flow with PKCE OAuth 2 详解(五):Device Authorization Flow OAuth 2 详解(三):Resource Owner Password Credentials Grant OAuth 2 详解(四):Client Credentials Flow OAuth 2 详解(二):Implict Grant Flow OAuth 2 详解(一):简介及 Authorization Code 模式 ElasticSearch 学习笔记 三种git流程以及发版模型 错误处理实践 权限模型(RBAC/ABAC) OIDC(OpenID Connect) 简介 任务队列简介 PostgreSQL 操作笔记 使用Drone CI构建CI/CD系统 Golang migrate 做数据库变更管理 使用PostgreSQL做搜索引擎 Nginx 源码阅读(三): 连接池、内存池 Nginx 源码阅读(二): 请求处理 Nginx 源码阅读(一): 启动流程 Go 泛型简明教程 KVM 显卡穿透给 Windows 使用 HTTP Router 处理 Telegram Bot 按钮回调 使用反射(reflect)对结构体赋值 GIN 是如何绑定参数的 你好 2022(2021 年终总结) 用Go导入大型CSV到PostgreSQL 使用 OpenWRT 搭建软路由 使用软KVM切换器 barrier 共享键鼠 SQL 防注入及原理 使用 gomock 测试 Go 代码 gevent不是黑魔法(二): gevent 实现 gevent不是黑魔法(一): greenlet 实现 用 entgo 替代 gorm 应用内使用crontab不是那么方便 单测时要不要 mock 数据库? Sentry 自建指南 用selenium完成自动化任务 用闲置的安卓手机做垃圾电话短信过滤 推荐三个时间管理工具 一次事故反思 当JS遇到uint64:JS整数溢出问题 SQLite3 存储以及ACID原理 Redis源码阅读:pub/sub实现 Redis源码阅读:zset实现 Redis源码阅读:bitmap 位图的运算 Redis源码阅读:set是怎么做交并集运算的?
程序员的MySQL手册(四):索引设计
Jiajun Huang · 2020-07-27 · via Jiajun的技术笔记

在了解了第三节的情况下,我们设计两个表,关系如下:

CREATE TABLE `user` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `name` varchar(255) NOT NULL,
  `passwd` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `user_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

CREATE TABLE `user_exam` (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `name` varchar(255) NOT NULL,
  `score` int NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4

接着我们分别插入10万行数据:

import random

from faker import Faker
import pymysql.cursors

fake = Faker()

# Connect to the database
connection = pymysql.connect(
    host='127.0.0.1',
    user='root',
    password='new_password',
    db='foo',
    charset='utf8mb4',
    cursorclass=pymysql.cursors.DictCursor,
)
connection.ping()

try:
    for i in range(100000):
        print(i, "...")
        fake_date = fake.date_this_month()
        with connection.cursor() as cursor:
            sql = "INSERT INTO `user` (`created_at`, `updated_at`, `name`, `passwd`) VALUES (%s, %s, %s, %s)"
            cursor.execute(sql, (fake_date, fake_date, "{}:{}".format(i, fake.name()), fake.name()))
            connection.commit()

        with connection.cursor() as cursor:
            sql = "INSERT INTO `user_exam` (`created_at`, `updated_at`, `name`, `score`) VALUES (%s, %s, %s, %s)"
            cursor.execute(sql, (fake_date, fake_date, fake.name(), random.randint(0, 100)))

        connection.commit()
finally:
    connection.close()

接着我们可以去看看 user 表中的查询效率区别,可以看到 user 表中,name 有索引,而 passwd 没有索引,我们看看查询效率:

MariaDB [foo]> select name, passwd from user limit 10,20;
+------------------+------------------+
| name             | passwd           |
+------------------+------------------+
| Regina Dawson    | Corey Smith Jr.  |
| Julie Jordan     | Jacob Reyes      |
| Amber Anderson   | Joe Floyd        |
| Tonya Jackson    | Joe Sosa         |
| Amy Armstrong    | Sandra Valentine |
| Jane Miller      | Daniel Booker    |
| Corey Mccarthy   | Roy Price        |
| Phillip Reynolds | Julie Wagner     |
| Jeffrey Schwartz | Shelia Clark     |
| Jessica Roberts  | Sandra Townsend  |
| Robert Rogers II | Sarah Church     |
| James Bennett    | David Rodriguez  |
| Billy Zimmerman  | Krista Lee       |
| Sheri Harris     | Billy Edwards    |
| Vicki Gomez      | Jose Dunn        |
| Craig Freeman    | Kevin Martin     |
| Anthony Brown    | Anthony Lynch    |
| Faith Carroll    | Michele Young    |
| Desiree Kelly    | Chelsea Smith    |
| Todd King        | Susan Rogers     |
+------------------+------------------+
20 rows in set (0.001 sec)

MariaDB [foo]> select COUNT(*) from user where name='Corey Mccarthy';
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set (0.001 sec)

MariaDB [foo]> select COUNT(*) from user where passwd='Sandra Townsend';
+----------+
| COUNT(*) |
+----------+
|        1 |
+----------+
1 row in set (0.019 sec)

通过有索引的 name 来查询,只需要0.001秒,而没有索引的 passwd 需要0.019秒。有同学可能要问了,这不是也挺快的吗? 看起来是挺快的,但是要注意两个基本事实:

  • 这个表的数据量并不大,才10万
  • 我用的这个虚拟机所在的宿主机,磁盘是SSD

如果脱离了这两个条件之一,速度还会更慢。由此我们可以看到索引的好处了,它可以极大的提升查询速度。但是,什么样的索引才是 好的索引呢?这是这次我们要探索的问题。

索引的类型

要想了解如何让索引更高效,我们必须先了解索引是如何工作的。常见的索引类型有两种,一种是B树索引,一种是哈希索引,接下来 我们分别介绍。

B树索引(以及B+树索引)

通常我们讲索引,就是讲的B树索引,因为数据库一般都会使用B树或者B+树来构建索引(MySQL大部分的引擎都支持B树,InnoDB使用B+树)。 MySQL的InnoDB实现中,B+树的叶子节点存储数据,且叶子节点之间有横向指针,可以直接顺序访问,叶子节点中包含数据所在的位置(一般是存储主键ID)。

使用B树(或B+树)索引,可以优化以下查询:

  • 全匹配查询,例如 user 的名字为 “Jhon Joes”, 查询为 SELECT * FROM user WHERE name = "Jhon Joes"
  • 匹配左半部分,例如 SELECT * FROM user WHERE name = "Jhon Joes" AND passwd = "blabal"
  • 匹配左边的一部分,例如 SELECT * FROM user WHERE name LIKE 'Jhon %'
  • 匹配一个范围的数据,例如 SELECT * FROM user WHERE name > 'A' AND name < 'Z'
  • 左边的列全匹配,加上一部分的模糊查询,例如 SELECT * FROM user WHERE name="Jhon Joes" AND passwd LIKE 'abcd%'
  • 仅查询索引的值,例如:SELECT name FROM user WHERE name="Jhon"

其实这些总结起来,就是传说中的最左匹配原则。具体可以见参考资料中给出的链接。

同时由于B树是有顺序的,MySQL还可以使用索引来进行排序。

哈希索引

对于哈希索引,我们可以回忆一下编程语言中常用的dict或者map。对于直接 求值是否相等的查询,哈希索引可以很快的给出答案,例如查询: SELECT * FROM user WHERE name="Jhon Joes", 但是哈希索引没有顺序所以无法用于排序,并且不能进行模糊查询,也无法 做范围查找。并且如果hash函数没有选好的话,冲突会很严重,因此性能会下降。

如何构建一个好的索引?

综上,我们可以知道索引可以有以下几个好处:

  • 通过查找索引,然后通过索引上保存的数据行的指针,我们可以减少所需要查询的数据量
  • 通过索引我们可以避免一部分查询的排序
  • 通过索引我么可以把随机I/O变成顺序I/O

那么,我们要怎么样才能构建一个足够高效的索引呢?

区分度

这里我们要引入一个新的概念,叫做区分度。什么叫区分度呢?顾名思义,在人群中一眼望去,越容易认出你来,那么你的区分度就越高。

对于数据的区分度,我们要怎么来确定呢?那就是对于一个给定的数据,在全部数据中,所占百分比越高,是不是就能 查询出更多的行数呢?是的,因此区分度就越低。 相反,如果给定一个数据,占的比例越低,那么区分度就越高。

举个例子,如果有一列,存储的数据是性别,只有三种可能:男,女,未知,那么这一列如果作为索引的话, 区分度是很低的,因为它无法把大量的不符合条件的行都过滤掉。但是如果 使用的是姓名的话,区分度就要高很多,因为重名的人不算多。但是如果是 根据主键来查询的话,那么区分度就是最高了,因为主键不会重复。

对于单列索引是这样的规则,对于多列的复合索引,也是一样的规则,在满足 查询条件的情况下,把区分度的列放在左边,把需要模糊查询的放在右边,这样可以获得最佳效能的索引。

索引的大小

前面我们说过,数据的类型,使用能够表示数据的越小的数据类型越好,对于索引也是一样,越小的索引,所需要的处理指令越少,占用 的内存硬盘也越少。

因此,尽可能选择区分度高,而数据大小比较小的列来做索引,对于复合索引, 如无必要,不使用,如使用,尽量简洁明要,选择区分度高的必要的列。

而对于过长的列,我们可以想办法选取其中的一部分来做索引,比如取一定范围的前缀,或者计算一个哈希值等。

聚簇索引(clustered index)

聚簇索引并不是一种索引类型,而是一种结合了索引的数据组织方式。MySQL的InnoDB的聚簇索引是把数据与一个B树索引存储在一起。

对于InnoDB来说,选择作为聚簇索引的索引,优先级如下:

  • 如果有主键,选择主键
  • 如果没有主键,选择第一个非空的UNIQUE键
  • 如果都没有,MySQL自己偷偷的生成一个隐藏的列来做

聚簇索引实现了如下的好处:

  • 通过聚簇索引,数据几乎是顺序存放的,比如1号user的数据和2号user的数据, 在磁盘上不会离得太远
  • 数据访问更快,比如以主键为聚簇索引,当通过其它查询查到行号为2的主键所在行时,只需要再通过聚簇索引 进行一次查找就可以把数据找出来
  • 如果只使用主键作为查询条件,那么一次就可以把所有数据查出来

其它几个索引相关的名词

  • secondary index(次级索引):一个表里,只有一个索引可以做成聚簇索引,剩下的其它索引,都叫次级索引。
  • covering index(覆盖索引):如果所需要的数据直接在索引里就可以找到,而不需要再去数据所在的聚簇索引查询一次,那么这次查询 就叫做实现了索引覆盖。

系列目录:


参考资料: