1 MySQL发展与里程碑
时间 | 里程碑 |
1996 年 |
MySQL 1.0发布。它的历史可以追溯到1979年,作者Monty用BASIC设计 的一个报表工具。 |
1996年10月 |
3.11.1发布:MySQL没有2.x版本: |
2000 年 |
ISAM升级成MylSAM引擎。MySQL开源。 |
2003 年 |
MySQL 4.0发布,集成InnoDB存储引擎。 |
2005 年 |
MySQL 5.。版本发布,提供了视图、存储过程等功能。 |
2008 年 |
MySQL AB公司被Sun公司收购,进入Sun MySQL时代。 |
2009 年 |
Oracle收购Sun公司,进入Oracle MySQL时代。 |
2010 年 |
MySQL 5.5发布,InnoDB成为默认的存储引擎。 |
2016 年 |
MySQL发布8.0.0版本。为什么没有6、7? 5.6可以当成6.x, 5.7可以当 成 7.Xo |
我们操作数据库有各种各样的方式,比如Linux系统中的命令行,比如数据库工具 Navicat,比如程序,例如Java语言的JDBC API或者ORM框架。
大家有没有思考过,当我们的工具或者程序连接到数据库之后,实际上发生了什么 事情?它的内部是怎么工作的?
就像我们到餐厅去吃饭,点了菜以后,过一会儿菜端上来了,后厨里面有哪些人? 他们分别做了什么事情?这个就涉及到MySQL的整体架构和工作流程了。
2 一条査询语句为例,我们来看下MySQL的工作流程是什么样的。
我们的程序或者工具要操作数据库,第一步要做什么事情?
跟数据库建立连接
2.1 连接
MySQL服务监听的端口默认是3306,客户端连接服务端的方式有很多。
可以是同步的也可以是异步的,可以是长连接也可以是短连接,可以是TCP也可以Unix Socket, MySQL有专门处理连接的模块,连接的时候需要验证权限。
我们怎么查看MySQL当前有多少个连接?
可以用show status命令,模糊匹配Thread:
show global status like 'Thread%';
字段 | 含义 | 案例数据 |
Threads_cached | 缓存中的线程连接数 |
82 |
Threads_connected | 当前打开的连接数 | 5 |
Threads_created | 为处理连接创建的线程数 | 87 |
Threads_running | 非睡眠状态的连接数,通常指并发连接数 | 2 |
问题:为什么连接数是查看线程?客户端的连接和服务端的线程有什么关系?
客户端每产生一个连接或者会话,在服务端就会创建一个线程来处理。反过来,如果要杀死会话,就是kill线程。
既然是分配线程的话,保持连接肯定会消耗服务端的资源,MySQL会把长时间不活动的(SLEEP)连接自动断开。
有两个参数:默认都是28800秒,8小时
show global variables like 'wait_timeout'; -- 非交互式超时时间,如 JDBC 程序
show global variables like 'interactive_timeout'; -- 交互式超时时间,如数据库工具
既然连接消耗资源,MySQL服务允许的最大连接数(也就是并发数)默认是多少呢?
在5.7版本中默认是151个,最大可以设置成100000
show variables like 'max_connections';
MySQL中的参数(变量)分为session和global级别,分别是在当前会话中生效和 全局生效,但是并不是每个参数都有两个级别,比如max_connections就只有全局级别。
当没有带参数的时候,默认是session级别,包括查询和修改。
比如修改了一个参数以后,在本窗口査询已经生效,但是其他窗口不生效:
show VARIABLES like 'autocommit';
set autocommit = on
所以,如果只是临时修改,建议修改session级别。 如果需要在其他会话中生效,必须显式地加上global参数。
执行一条查询语句,客户端跟服务端建立连接之后呢?下一步要做什么?
2.2 查询缓存
MySQL内部自带了一个缓存模块。
有一张500万行数据的表,没有索引,如果我两执行一模一样的SQL语句,第二次会不会变得很快?
select * from user_innodb where name='bonnie';
缓存没有生效,为什么?MySQL的缓存默认是关闭的。
show variables like '%query_cache%';
默认关闭的意思就是不推荐使用,为什么MySQL不推荐使用它自带的缓存呢? 主要是因为MySQL自带的缓存的应用场景有限,第一个是它要求SQL语句必须一 模一样,中间多一个空格,字母大小写不同都被认为是不同的的SQL。第二个是表里面任何一条数据发生变化的时候,这张表所有缓存都会失效,所以对 于有大量数据更新的应用,也不适合。
所以缓存这一块,我们还是交给0RM框架(比如MyBatis默认开启了一级缓存), 或者独立的缓存服务,比如Redis来处理更合适。MySQL 8.0中,查询缓存已经被移除了。
2.3 语法解析和预处理(Parser & Preprocessor)
为什么一条SQL语句能够被识别呢?假如随便执行一个字符串bonnie,服务器会报一个1064的错误。
为什么这块会报错呢?这块就是MySQL的Parser解析器和Preprocessor预处理模块。
这一步主要做的事情是对语句基于SQL语法进行词法和语法分析和语义的解析。
2.2.1 词法解析
词法分析就是把一整个SQL语句打碎成一个个的单词,比如一个简单的SQL语句。
select name from user where id=1;
它会被打碎成8个符号,每个符号是什么类型,从哪里开始到哪里结束。
1.2.2 语法分析
第二步就是语法分析,语法分析会对SQL做一些语法检查,比如单引号有没有闭合, 然后根据MySQL定义的语法规则,根据SQL语句生成一个数据结构。这个数据结构我们把它叫做解析树(select_lex)。
词法语法分析是一个非常基础的功能,Java的编译器、百度搜索引擎如果要识别语句,必须也要有词法语法分析功能。
任何数据库的中间件,要解析SQL完成路由功能,也必须要有词法和语法分析功能, 比如Mycat, Sharding-JDBC (用到了 Druid Parser)。在市面上也有很多的开源的词 法解析的工具(比如LEX, Yacc)
2.2.3 预处理器
问题:如果我写了一个词法和语法都正确的SQL,但是表名或者字段不存在,会在哪里报错?是在数据库的执行层还是解析器?
比如:
select * from bonnie;
我想了想,似乎解析器可以分析语法,但是它怎么知道数据库里面有什么表,表里面有什么字段呢?
实际上还是在解析的时候报错,解析SQL的环节里面有个预处理器。
它会检査生成的解析树,解决解析器无法解析的语义。比如,它会检査表和列名是否存在,检査名字和别名,保证没有歧义。预处理之后得到一个新的解析树。
2.4 查询优化(Query Optimizer)与查询执行计划
2.4.1. 什么是优化器?
得到解析树之后,是不是执行SQL语句了呢?
这里我们有一个问题,一条SQL语句是不是只有一种执行方式?或者说数据库最终执行的SQL是不是就是我们发送的SQL?
这个答案是否定的。一条SQL语句是可以有很多种执行方式的,最终返回相同的结
果,他们是等价的。但是如果有这么多种执行方式,这些执行方式怎么得到的?最终选 择哪一种去执行?根据什么判断标准去选择?
这个就是MySQL的査询优化器的模块(Optimizer)。
査询优化器的目的就是根据解析树生成不同的执行计划(Execution Plan),然后选 择一种最优的执行计划,MySQL里面使用的是基于开销(cost)的优化器,那种执行计 划开销最小,就用哪种。
可以使用这个命令査看査询的开销:
show status like 'Last_query_cost';
2.4.2 优化器可以做什么?
MySQL的优化器能处理哪些优化类型呢?
举两个简单的例子:
1、当我们对多张表进行关联查询的时候,以哪个表的数据作为基准表。
2、有多个索弓|可以使用的时候,选择哪个索引。实际上,对于每一种数据库来说,优化器的模块都是必不可少的,他们通过复杂的
算法实现尽可能优化查询效率的目标。如果对于优化器的细节感兴趣,可以看看《数据库査询优化器的艺术-原理解析与SQL性能优化》
但是优化器也不是万能的,并不是再垃圾的SQL语句都能自动优化,也不是每次都能选择到最优的执行计划,大家在编写SQL语句的时候还是要注意。
优化完之后,得到一个什么东西呢?优化器最终会把解析树变成一个査询执行计划,查询执行计划是一个数据结构
我们怎么査看MySQL的执行计划呢?比如多张表关联查询,先査询哪张表?在执行査询的时候可能用到哪些索引,实际上用到了什么索引?
MySQL提供了一个执行计划的工具。我们在SQL语句前面加上EXPLAIN,就可以看到执行计划的信息。
EXPLAIN select name fiom user where id=1;
如果要得到详细的信息,还可以用FORMAT=JSON,或者开启optimizer trace。
EXPLAIN FORMAT=JSON select name from user where id=1:
2.5 存储引擎
得到执行计划以后,SQL语句是不是终于可以执行了?
问题又来了:
1、从逻辑的角度来说,我们的数据是放在哪里的,或者说放在一个什么结构里面?
2、执行计划在哪里执行?是谁去执行?
2.5.1.存储引擎基本介绍
比如这三张表,都是二维的存储结构,表面上看起来是一模一样的
表名:user_innodb、user_memory、user_myisam
但是他们的表类型是不一样的,存储引擎分别是innodb、memory、myisam,底层肯定是有差异的,我们先找到数据库存放数据的路径:
show variables like 'datadir'; -- 查询数据存放的目录
默认情况下,每个数据库有一个自己文件夹,以bonnie数据库为例。
任何一个存储引擎都有一个frm文件,这个是表结构定义文件。
user_innodb.frm
user_innodb.ibd
user_memory.frm
user_myisam.frm
user_myisam.MYD
user_myisam.MYI
不同存储引擎存放数据的方式不一样,产生的文件也不一样,innodb是一个,memory没有,myisam是两个。
思考:
问题 |
1、表类型是怎么选择的?可以修改吗? |
2、MySQL为什么支持这么多存储引擎呢?一种还不够用吗? |
3、这些不同的存储引擎,到底有什么区别? |
2.5.2 存储引擎选择
一张表的存储引擎,是在创建的时候指定的,使用ENGINE关键字。
CREATE TABLE `user_innodb` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`gender` tinyint(1) DEFAULT NULL,
`phone` varchar(11) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';
往往我们在创建数据库表的时候并没有指定对应的存储引擎,数据库就会使用默认的存储引擎,在5.5.5之前,默认的存储引擎是MyISAM,5.5.5之后,默认的存储引擎是InnoDB. 这么多的存储引擎,区别到底在哪里?
试想一下: 如果我有一张表,需要很高的访问速度,而不需要考虑持久化的问题,是不是要把数据放在内存?
如果一张表,是用来做历史数据存档的,不需要修改,也不需要索引,那它是不是要支持数据的压缩?
如果一张表用在读写并发很多的业务中,是不是要支持读写不干扰,而且要保证比较高的数据一致性呢?
说到这里大家应该明白了,为什么要支持这么多的存储引擎,就是因为我们有不同的业务需求,一种存储引擎不能提供所有的特性。
2.5.3 存储引擎比较
常见的存储引擎:第15章:存储引擎和表类型_MySQL 中文文档
官网地址: https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html
Innodb引擎: https://dev.mysql.com/doc/refman/8.4/en/innodb-storage-engine.html
MylSAM (3个文件):
应用范围比较小,表级锁定限制了读/写的性能,因此在web和数据仓库配置中,他通常用于只读或以读为主的工作。
特点:
支持表级别的锁(插入和更新会锁表)不支持事务
拥有较高的插入(insert)和查询(select)速度
存储了表的行数(count速度更快)
(怎么快速向数据库插入100万条数据?我们有一种先用MylSAM插入数据,然后修改存储引擎为 InnoDB的操作。)
适合:只读之类的数据分析的项目。
Innodb(两个文件)
mysql 5.7中的默认存储引擎。InnoDB是一个事务安全(与ACID兼容)的MySQL 存储引擎,它具有提交、回滚和崩溃恢复功能来保护用户数据。InnoDB行级锁(不升级为更粗粒度的锁)和Oracle风格的一致非锁读提高了多用户并发性和性能。InnoDB将 用户数据存储在聚集索引中,以减少基于主键的常见查询的I/O。为了保持数据完整性, InnoDB还支持外键引用完整性约束。
特点:
支持事务,支持外键,因此数据的完整性、一致性更高。
支持行级别的锁和表级别的锁。
支持读写并发,写不阻塞读(MVCC)。
特殊的索引存放方式,可以减少I。,提升査询效率。
适合:经常更新的表,存在并发读写或者有事务处理的业务系统。
Memory (1个文件)
将所有数据存储在RAM中,以便在需要快速查找非关键数据的环境中快速访问。这 个引擎以前被称为堆引擎。其使用案例正在减少;InnoDB及其缓冲池内存区域提供了一 种通用、持久的方法来将大部分或所有数据保存在内存中,而ndbduster为大型分布式 数据集提供了快速的键值查找。
特点:
把数据放在内存里面,读写的速度很快,但是数据库重启或者崩溃,数据会全部消失。只适合做临时表。将表中的数据存储到内存中。
CSV (3个文件)
它的表实际上是带有逗号分隔值的文本文件。csv表允许以CSV格式导入或转储数据, 以便与读写相同格式的脚本和应用程序交换数据。因为CSV表没有索引,所以通常在正 常操作期间将数据保存在innodb表中,并且只在导入或导出阶段使用csv表。
特点:不允许空行,不支持索引。格式通用,可以直接编辑,适合在不同数据库之 间导入导出。
Archive (2 个文件)
这些紧凑的未索引的表用于存储和检索大量很少引用的历史、存档或安全审计信息。 特点:不支持索引,不支持update deleteo
这是MySQL里面常见的一些存储引擎,我们看到了,不同的存储引擎提供的特性都 不一样,它们有不同的存储机制、索引方式、锁定水平等功能。
我们在不同的业务场景中对数据操作的要求不同,就可以选择不同的存储引擎来满 足我们的需求,这个就是MySQL支持这么多存储引擎的原因。
2.5.4 如何选择存储引擎?
如果对数据一致性要求比较高,需要事务支持,可以选择InnoDB。
如果数据查询多更新少,对查询性能要求比较高,可以选择MyISAM。
如果需要一个用于查询的临时表,可以选择Memory。
如果所有的存储引擎都不能满足你的需求,并且技术能力足够,可以根据官网内部 手册用C语言开发一个存储引擎:
https://dev.mvsql.com/doc/internals/en/custom-engine.html
按照这个开发规范,实现相应的接口,给执行器操作。
也就是说,为什么能支持这么多存储引擎,还能自定义存储引擎,表的存储引擎改了对Server访问没有任何影响,就是因为大家都遵循了一定了规范,提供了相同的操作接口。
每个存储引擎都有自己的服务。
show engine innodb status;
这些存储引擎用不同的方式管理数据文件,提供不同的特性,但是为上层提供相同的接口。
2.6.执行引擎(Query Execution Engine),返回结果
OK,存储引擎分析完了,它是我们存储数据的形式,继续第二个问题,是谁使用执行计划去操作存储引擎呢?
这就是我们的执行引擎,它利用存储引擎提供的相应的API来完成操作。
为什么我们修改了表的存储引擎,操作方式不需要做任何改变?因为不同功能的存 储引擎实现的API是相同的。
最后把数据返回给客户端。