前言:
Mysql可能是每个程序员的必修课,可以说是使用起来是没有什么问题的,但是作为一名合格的程序猿,深入学习Mysql的内部工作原理是非常有必要的,主要是理解和学习Mysql的底层思想,希望在日后如遇到一些"搜索"操作,能够举一反三!!
接下来可能会先去探索整个数据库的工作流程,后续会再说明其中的一些细节,例如INNODB、MYISUM等搜索引擎~
Mysql的存储数据:
创建数据库:
当我们使用create创建一个数据库,此时对应的在磁盘上会生成一个以该数据库命名的文件夹,如下:
每创建一个数据库,此处的文件就多一个。当然该数据库的相关配置信息也会被记录在一个例如后缀为.ibd的文件中
我们默认上述创建的数据库选用的是INNODB引擎,并配用UTF-8mb4的字符集。
- 元数据存储:
- 系统表更新:MySQL 会在系统数据库(主要是
mysql
数据库和information_schema
数据库)的相关表中记录新数据库的元数据。
- 在
information_schema.SCHEMATA
表中,会新增一条记录,包含数据库的名称、默认字符集、默认排序规则等信息。例如,使用CREATE DATABASE test_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
创建数据库时,这些字符集和排序规则信息会被准确记录。mysql.db
表也会更新,存储与该数据库相关的权限信息,确保后续可以对数据库的访问权限进行管理。
- 数据字典更新:数据字典是 MySQL 存储数据库元数据的重要组成部分,它会同步更新新数据库的元数据信息,为后续数据库对象的管理和操作提供基础。
上图展示的是用户特殊的数据库权限(数据库级别),下图是可以登录mysql的所有用户,与用户相关权限(全局级别):
- 文件系统操作:MySQL 会在文件系统中创建一个与数据库同名的目录,用于存储该数据库下的所有对象,如表、索引等。例如,创建名为
test_db
的数据库,在文件系统的数据库存储目录下会生成一个test_db
目录。不同存储引擎可能还会在该目录下创建一些特定的文件用于存储数据和索引等。如 InnoDB 存储引擎的独立表空间模式下,每个表会有一个.ibd
文件存储在该数据库目录下。
(一般是每个表都有自己的.idb的文件,前提是使用INNODB搜索引擎)
查找数据库:
当我们数据库创建结束后,当我们想使用数据库时一般是需要查看当前有哪些数据库,一般使用show databases语句。
此是mysql书如何完成扫描所有数据库的呢?
分为以下五步:
- 查询解析:MySQL 服务器接收到
SHOW DATABASES
语句后,首先对其进行词法分析和语法分析。词法分析将语句分解为一个个的单词,如SHOW
、DATABASES
等,语法分析则检查语句是否符合 MySQL 的语法规则。如果语句语法有误,服务器会返回错误信息。- 权限检查:在执行查询之前,MySQL 会检查当前用户是否具有查看数据库列表的权限。只有具有相应权限的用户才能执行
SHOW DATABASES
操作。如果用户没有足够的权限,服务器将拒绝执行该查询,并返回权限不足的错误信息。- 元数据获取:MySQL 会从系统表中获取数据库的元数据信息。这些元数据存储在系统数据库(通常是
mysql
数据库)的相关表中,例如mysql.db
表或information_schema.SCHEMATA
表。mysql.db
表存储了数据库的授权信息等,information_schema.SCHEMATA
表提供了关于数据库的各种元数据,包括数据库名称、创建时间、字符集等。MySQL 通过查询这些系统表来获取所有数据库的相关信息。- 结果集生成:根据从系统表中获取的元数据信息,MySQL 生成包含所有数据库名称的结果集。结果集中的每一行对应一个数据库,通常只包含数据库名称这一列信息。
- 结果返回:将生成的结果集返回给客户端。如果是在命令行中执行
SHOW DATABASES
,结果会以表格的形式显示在终端上;如果是通过编程语言中的 MySQL 客户端库执行该语句,结果会以相应编程语言的数据结构形式返回,供程序进一步处理。
以上的操作都是围绕着mysql.idb相关文件中的内容进行查找的,至于是不是以遍历的方式进行查找的,这是不一定的,一般是根据索引的方式查找~~ (与存储时选定的搜索引擎有关)
创建数据表
当我们创建完数据库之后,就可以开始创建该库中的相关表了,一般创建表会使用如下的sql语句:
use 数据库;
create table 表名 (字段名 字段类型(int bigint char varchar ...) 指定是否可以为空 指定是否自增 指定注解......)
当我们创建完数据库,mysql会分以下三步进行:
- 元数据记录
- MySQL 会在系统表中记录新表的元数据信息。对于 InnoDB 存储引擎,会在
mysql.innodb_table_stats
和information_schema.TABLES
等表中记录表的基本信息,如名称、创建时间、存储引擎、表空间等。- 同时,会在
information_schema.COLUMNS
表中记录表的列信息,包括列名、数据类型、约束条件等。![]()
information_schema.TABLES ![]()
mysql.innodb_table_stats
- 存储引擎交互
- 不同的存储引擎在创建表时的操作有所不同。以 InnoDB 为例,会为新表分配一个独立的表空间(如果开启了独立表空间模式),并创建对应的
.ibd
文件来存储表的数据和索引。- 存储引擎会初始化数据结构,如创建 B+ 树索引结构(如果有索引定义),为后续的数据插入和查询操作做好准备。
- 文件系统操作
- MySQL 会在文件系统中执行相应的操作。对于 InnoDB 存储引擎,会在数据库目录下创建与表对应的
.ibd
文件。对于 MyISAM 存储引擎,会创建三个文件:.frm
文件存储表的定义,.MYD
文件存储表的数据,.MYI
文件存储表的索引。
查询数据表
当输入:
SELECT * FROM users WHERE age > 18;
之后会发生什么?
1.检查缓存:
首先Mysql也是向缓存中检查之前有没有同样的sql语句,如果有直接返回之前的结果,就不再进行下一步操作了。
2.解析查询语句:
检查下查询语句的语法是否有误,如果没与错误,会将关键字、查询条件解析出来发送给mysql服务器。
3.生成查询计划:
服务器收到了客户端发来的解析过后的语法,MySQL 的查询优化器会根据表的索引情况、统计信息等,生成多个可能的查询执行计划,并评估每个计划的成本(如磁盘 I/O 次数、CPU 开销等),选择成本最低的执行计划。例如,如果
users
表的age
列上有索引,优化器可能会选择使用该索引来加快查询速度。
4.执行查询计划:
- 访问存储引擎:根据生成的查询执行计划,服务器调用相应的存储引擎(如 InnoDB、MyISAM 等)来执行具体的查询操作。存储引擎负责从磁盘或内存中读取数据,并根据查询条件进行过滤和排序。
- 数据读取与过滤:存储引擎根据执行计划读取满足条件的数据行。例如,对于上述查询,存储引擎会扫描
users
表,找出age
大于 18 的记录。如果使用了索引,存储引擎会利用索引快速定位到符合条件的记录,减少不必要的磁盘 I/O 操作。
5.返回结果:
- 结果集处理:存储引擎将满足查询条件的数据行返回给服务器,服务器对这些数据进行进一步处理,如排序、分组、聚合等(如果查询语句中有相应的操作)。
- 结果返回客户端:最后,服务器将处理好的结果集返回给客户端。客户端接收到结果后,会以合适的方式显示给用户。
查询计划的补充:
查询计划是根据自身写的sql语句中的条件生成,如果查询计划中有引入索引列(前提是索引列生效),会生成较为快的查询方式,如果查询条件中没有引入索引列,则会根据默认的索引生成查询计划。
此时涉及到索引,那么此处需要展开对索引的一些了解:
索引的分类如下:
普通索引:
作为普通索引的数据列,对该列中的数据没有其他要求,可以为空,也可以是重复的数据。
SQL语句如下:
create table test_db (id int comment'主键id',
user_name varchar(20) comment'用户名称'
,age int comment'用户年龄',
INDEX idx_name (user_name));
此时如果在查询时存在索引作为查询条件,会生成较快的查询策略!!
唯一索引:
作为唯一索引的数据列,该列中的数据不能有重复,但是数据可以为NULL。
SQL语句如下:
create table product (id int COMMENT'主键id',
product_code varchar(20) COMMENT'产品id',
name varchar(20) COMMENT'产品名',
UNIQUE INDEX product_id (product_code));
主键索引:
作为主键索引的数据列,该列必须为设置为主键,该列中的数据不能重复,不能为NULL。
SQL语句如下:
CREATE TABLE orders (order_id INT PRIMARY KEY,customer_id INT,order_date DATE
);
全文索引:
一般是将数据类型为test文本类型的数据列设置为全文索引,全文索引可以对大文本字段(如文章内容、评论等)进行分词处理,并建立索引,以支持高效的全文搜索。
SQL语句如下:
CREATE TABLE articles (id INT,title VARCHAR(200),content TEXT,FULLTEXT INDEX idx_content (content)
);
联合索引:
可以同时将多个列设置为索引,但是查询时,查询条件必须遵守最左前缀原则:
SQL语句如下:
CREATE TABLE employees (id INT,first_name VARCHAR(50),last_name VARCHAR(50),INDEX idx_name (first_name, last_name)
);
最左前缀原则:
查询时,查询条件从左至右依次符合匹配作为索引列的索引顺序,否则索引失效:
当查询条件经常同时涉及多个列时,使用复合索引可以提高查询效率。例如
SELECT * FROM employees WHERE first_name = 'John' AND last_name = 'Doe';
,如果first_name
和last_name
上有复合索引,查询会更快。
执行查询计划补充:
当筛选出最佳查询计划之后,这时候就需要将查询计划告诉搜索引擎,然后执行查询计划。
此处涉及到相关的搜索引擎的概念,我们需要简单了解一下:
InnoDB:
InnoDB是现在Mysql中默认的搜索引擎。特点如下:
1.支持事务:确保了ACID(原子性、一致性、隔离性、持久性),保证了数据的完整性。
2.支持行级锁:采用行级锁,在高并发的场景下,可以精细的对数据进行控制,减少了所冲突的概率。
3.支持外键约束:可以通过外键建立表与表之间的关联关系,保证数据的参照完整性。
4.聚簇索引:数据和索引存储在一起,以主键为索引结构,能快速查询主键相关的数据。(此处的聚簇索引中的索引必须是主键索引)
此时肯定是有一些疑问的,普遍疑问如下:
InnoDB是如何支持事务的?
1.原子性:
原子性指事务中的所有操作要么全部成功,要么全部失败回滚。InnoDB 主要利用 回滚日志(Undo Log) 来实现原子性
- 记录操作:在执行事务中的每一个修改操作(如插入、更新、删除)之前,InnoDB 会将操作的逆操作记录到回滚日志中。例如,当执行一个删除操作时,回滚日志会记录被删除记录的原始数据;当执行一个更新操作时,回滚日志会记录更新前的数据值。
- 回滚机制:如果事务在执行过程中出现错误或者用户手动发起回滚操作,InnoDB 会根据回滚日志中的信息,将数据恢复到事务开始之前的状态,保证事务中的所有操作都没有对数据库产生影响。
2.一致性;
要求事务将数据库从一个一致状态转换到另一个一致状态。InnoDB 通过以下几种方式来协助实现一致性:
- 事务隔离级别:不同的隔离级别可以防止不同类型的并发问题(如脏读、不可重复读、幻读),从而保证数据在事务执行前后的一致性。例如,可串行化隔离级别可以确保事务串行执行,避免并发问题。
- 约束检查:在事务执行过程中,InnoDB 会检查数据是否满足数据库的约束条件,如主键约束、唯一约束、外键约束等。如果违反了这些约束,事务会被回滚,以保证数据的一致性。
- 回滚和提交:通过回滚日志和提交机制,确保事务要么全部完成,要么全部撤销,从而维护数据库的一致性。
3.隔离性;
隔离性保证多个事务并发执行时,彼此之间互不干扰。InnoDB 提供了四种事务隔离级别,通过 锁机制 和 多版本并发控制(MVCC) 来实现隔离性:
- 锁机制
- 共享锁(S 锁):多个事务可以同时对同一资源加共享锁,用于读操作,相互之间不冲突。例如,多个事务可以同时对一条记录加共享锁进行读取。(加了共享锁的资源,就不能再加排他锁(写锁)了!)
- 排他锁(X 锁):一个事务对资源加排他锁后,其他事务不能再对该资源加任何类型的锁,用于写操作。例如,一个事务对一条记录加排他锁后,其他事务不能对该记录进行读取或修改。
- 意向锁:用于表示事务对某个资源(如表或行)有更高层次的锁需求,提高锁的管理效率。
- 多版本并发控制(MVCC):MVCC 是 InnoDB 实现高并发的关键技术之一。它通过为每个数据行维护多个版本,使得读操作可以在不获取锁的情况下进行,从而避免了读写冲突。在 MVCC 中,读操作读取的是数据的快照版本,而写操作会创建新的数据版本。不同的隔离级别对 MVCC 的使用方式有所不同,例如,在可重复读隔离级别下,一个事务在执行期间看到的数据版本是一致的。 (后续有介绍)
隔离级别:
- 读未提交(Read Uncommitted)
- 描述:在该隔离级别下,事务可以读取到其他事务尚未提交的数据,这可能导致脏读。
- 示例:事务 A 修改了一条记录但未提交,此时事务 B 可以读取到事务 A 修改后的值。如果事务 A 回滚,那么事务 B 读取到的数据就是无效的,即脏数据。
- 特点:隔离级别最低,并发性能最高,但可能出现脏读、不可重复读和幻读等问题。
- 读已提交(Read Committed)
- 描述:事务只能读取到其他事务已经提交的数据,避免了脏读,但可能会出现不可重复读的情况。
- 示例:事务 A 在两次读取同一数据之间,事务 B 修改并提交了该数据,那么事务 A 两次读取到的值可能不同。
- 特点:可以避免脏读,但是在同一事务中多次读取相同数据时,可能会因为其他事务的提交而得到不同的结果,无法保证可重复读。
- 可重复读(Repeatable Read)
- 描述:在同一个事务中,多次读取同一数据时,得到的结果是一致的,它解决了脏读和不可重复读的问题,但可能会出现幻读。
- 示例:事务 A 在读取某些数据后,事务 B 插入了一条符合事务 A 查询条件的新记录并提交,当事务 A 再次以相同条件查询时,会发现多了一条记录,就好像产生了幻觉一样。
- 特点:默认的隔离级别,保证了事务在多次读取相同数据时的一致性,但对于新增数据的并发操作可能存在幻读问题。
- 串行化(Serializable)
- 描述:这是最高的隔离级别,它强制事务串行执行,即每个事务都必须等待前一个事务完成后才能执行,从而避免了所有的并发问题,包括脏读、不可重复读和幻读。
- 示例:如果有多个事务同时对同一数据进行操作,在串行化隔离级别下,这些事务会按照顺序依次执行,就像在单线程环境中一样。
- 特点:隔离级别最高,能确保数据的一致性,但并发性能最低,因为所有事务都只能串行执行,在高并发场景下可能会导致性能瓶颈。
多版本并发控制:
基本原理
- MVCC 在每行数据后面添加了两个隐藏的列,分别是创建版本号和删除版本号。
- 当一个事务插入一行数据时,会将当前系统的版本号(通常是一个递增的数字)写入创建版本号列。
- 当事务更新或删除一行数据时,实际上并不是真正删除或立即更新数据,而是将当前系统版本号写入删除版本号列,并在当前行的位置插入一条新的数据记录(对于更新操作),新记录的创建版本号为当前系统版本号。
工作机制
- 读操作:当一个事务进行读操作时,它只会读取创建版本号小于或等于当前事务版本号,且删除版本号大于当前事务版本号(或为空)的数据行。这样可以确保读取到的数据是在当前事务开始之前已经提交的数据,避免了读取到未提交的脏数据。
- 写操作:写操作(插入、更新、删除)会创建新的版本记录,并更新相应的版本号。在更新或删除操作时,MVCC 会先检查要操作的数据行的当前版本号与事务的版本号是否一致,如果不一致,说明数据在其他事务中已经被修改,可能会导致冲突,这时就需要根据一定的策略来处理冲突,如回滚事务或进行重试等。
优点
- 提高并发性能:读操作和写操作可以并发执行,不会因为读操作而阻塞写操作,也不会因为写操作而阻塞读操作,大大提高了数据库的并发处理能力。
- 减少锁竞争:相比于基于锁的并发控制机制,MVCC 减少了锁的使用,从而降低了锁竞争带来的性能开销和死锁风险。
4.持久性:
持久性确保一旦事务提交,其对数据库的修改就会永久保存,即使在系统崩溃或故障的情况下也不会丢失。InnoDB 主要通过 重做日志(Redo Log) 来实现持久性:
- 记录修改:在事务执行过程中,InnoDB 会将对数据页的修改操作记录到重做日志中。重做日志采用顺序写入的方式,性能较高。
- 崩溃恢复:当系统崩溃后重启时,InnoDB 会根据重做日志中的记录,将未完成的事务对数据库的修改重新应用到数据页上,从而保证数据的持久性。
综上所述,InnoDB 通过回滚日志、锁机制、多版本并发控制和重做日志等技术,实现了事务的 ACID 特性,为数据库的可靠性和一致性提供了有力保障。
InnoDB通过聚簇索引为什么能够加快查询速度?工作原理?
原理:
1.在聚簇索引中,数据页和索引页是紧密关联的。叶子节点包含了完整的数据行,而不仅仅是索引键和指针。这意味着当通过聚簇索引查找数据时,在找到索引节点的同时也就找到了对应的数据,无需再进行额外的磁盘 I/O 操作来获取数据,构建了一棵B+树。
2.并且叶子节点与相邻的叶子节点通过指针构成双向链表,再遍历也在子节点时也比较方便。
示例:
假设要查询一个具有聚簇索引的表中某一特定主键值的记录。数据库引擎会从聚簇索引的根节点开始搜索,通过比较索引值逐步向下导航到叶子节点,而叶子节点直接存储了对应的数据行,这样可以快速获取到所需的数据。
insert相关插入:
通过sql语句,其中的insert语句是插入语句,如果一张表存在,我们可以通过insert语句插入数据到mysql或类似的sql型数据库中。
那么从用户输入sql到数据存储到数据库整个流程是怎样的呢?
1.语法检查:mysql首先是会检查语法是否有误。
2.权限检查:当语法检查的过关以后,就会进入权限的检查。
3.解析并优化:将sql语句解析为内部的数据结构,并且根据条件进行语句的优化。(Insert优化较少)
4.存储引擎处理:根据最开始选择的存储引擎执行解析后的语句,若当前语句是放在事务中执行的,遵循ACID特性。进行锁机制、多版本并发控制的处理。
5.索引更新:若数据表中存在索引,则将索引结构进行更新。
6.写入日志:为了方便后期进行事务的回滚(UNDO LOG)或者系统崩溃后恢复(REDO LOG)。
7.提交或回滚:
- 事务提交:若当前会话处于事务中,并且执行了
COMMIT
语句,那么插入操作会被永久保存到数据库中。- 事务回滚:若执行了
ROLLBACK
语句,插入操作会被撤销,数据库会恢复到插入操作之前的状态。
delete删除操作:
delete删除操作与insert操作是类似的,这里不做重复的解释了!!