mysql 存储结构介绍及执行过程分析

MySQL体系结构介绍

 

1 mysql 的体系结构

 

 

MySQL整体的逻辑结构可以分为4层,客户层、服务层、存储引擎层、数据层

客户层
  • 客户层:进行相关的连接处理、权限控制、安全处理等操作
服务层
  • 服务层负责与客户层进行连接处理、处理以及执行SQL语句等,主要包含连接器、查询缓存、优化器、执行器、存储引擎。触发器、视图等也在这一层
  • 连接池:存储和管理客户端与数据库的连接,一个线程负责管理一个连接并处理这个连接上的sql请求。

  • 缓存:缓存把执行过的语句和结果集以key-value对的形式存储。

    如果查询能够命中,那么结果集被直接返回给客户端。

    如果在缓存未命中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。

    查询缓存往往弊大于利。因为查询缓存的失效非常频繁,对一个表的某一条数据更新,这个表上所有的查询缓存都会被清空。

     

存储引擎层
  • 存储引擎层负责对数据的存储和提取,常见的存储引擎有InnoDB、MyISAM、Memory等,在MySQL5.5之后,MySQL默认的存储引擎就是InnoDB,InnoDB默认使用的索引结构就是B+树,上面的服务层就是通过API接口与存储引擎层进行交互的
  • 存储引擎:存储引擎有多种类型,存储引擎的作用是负责文件数据的存储,每种存储引擎提供了相同名称但具体实现不同的接口给用户层。不同类型引擎的存储机制、索引实现和锁功能不同,这是为了让用户适应多种应用场景的需求。

  • 需要特别注意,存储引擎只基于表,而非数据库。
数据层
  • 数据层系主要包括MySQL中存储数据的底层文件,与上层的存储引擎进行交互,是文件的物理存储层。其存储的文件主要有:日志文件、数据文件、配置文件、MySQL的进行pid文件和socket文件等。
    那么一条SQL语句在MySQL的整个体系结构是如何执行的呢?
  • 文件系统:属于操作系统层面,mysql应用层可以调用操作系统提供的系统调用操作文件,发起IO请求。

2、SQL语句的执行过程

当向MySQL发送一条SQL语句的时候

1、客户层
  • 首先连接器与客户端进行连接、以linux系统为例,通过在Mysql服务启动成功之后通过一下命令进行数据库的登录
[root@bp18425116f0cojd1vnz ~]# mysql -uroot -p
Enter password: 

如果密码输入错误的话就会有以下提示

ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES

如果出现MySQL密码忘记的情况下,可以通过以下方法进行登录

1、查询MySQL服务是否启动,如若启动,关闭MySQL服务
[root@bp18425116f0cojd1vnz ~]# ps -ef |grep mysql
root       87531       1  0 Feb09 ?        00:00:00 /bin/sh /www/server/mysql/bin/mysqld_safe --defaults-file=/etc/my.cnf --datadir=/www/server/data --pid-file=/www/server/data/bp18425116f0cojd1vnz.pid --sql-mode=NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
mysql      88147   87531  0 Feb09 ?        00:43:28 /www/server/mysql/bin/mysqld --defaults-file=/etc/my.cnf --basedir=/www/server/mysql --datadir=/www/server/data --plugin-dir=/www/server/mysql/lib/plugin --user=mysql --sql-mode=NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION --log-error=bp18425116f0cojd1vnz.err --open-files-limit=65535 --pid-file=/www/server/data/bp18425116f0cojd1vnz.pid --socket=/tmp/mysql.sock --port=3306
root     2725702 2724782  0 14:35 pts/0    00:00:00 grep --color=auto mysql
2、关闭MySQL服务
[root@bp18425116f0cojd1vnz ~]# systemctl stop mysql
[root@bp18425116f0cojd1vnz ~]# systemctl status mysql
● mysqld.service - LSB: start and stop MySQL
   Loaded: loaded (/etc/rc.d/init.d/mysqld; generated)
   Active: inactive (dead) since Thu 2022-06-09 14:36:55 CST; 2s ago
     Docs: man:systemd-sysv-generator(8)
  Process: 2725788 ExecStop=/etc/rc.d/init.d/mysqld stop (code=exited, status=0/SUCCESS)

Feb 09 14:30:10 bp18425116f0cojd1vnz systemd[1]: Starting LSB: start and stop MySQL...
Feb 09 14:30:11 bp18425116f0cojd1vnz mysqld[1174]: /etc/rc.d/init.d/mysqld: line 244: my_print_defaults: command not found
Feb 09 14:30:11 bp18425116f0cojd1vnz mysqld[1174]: /etc/rc.d/init.d/mysqld: line 265: cd: /www/server/mysql: No such file or directory
Feb 09 14:30:11 bp18425116f0cojd1vnz mysqld[1174]: Starting MySQLCouldn't find MySQL server (/www/server/mysql/bin/mysqld_safe)[FAILED]
Feb 09 14:30:11 bp18425116f0cojd1vnz systemd[1]: Started LSB: start and stop MySQL.
Jun 09 14:36:52 bp18425116f0cojd1vnz systemd[1]: Stopping LSB: start and stop MySQL...
Jun 09 14:36:55 bp18425116f0cojd1vnz mysqld[2725788]: Shutting down MySQL..[  OK  ]
Jun 09 14:36:55 bp18425116f0cojd1vnz systemd[1]: mysqld.service: Succeeded.
Jun 09 14:36:55 bp18425116f0cojd1vnz systemd[1]: Stopped LSB: start and stop MySQL.
3、修改 vim /etc/my.cnf

在/etc/my.cnf 添加一行 skip-grant-tables

[client]
#password       = your_password
port            = 3306
socket          = /tmp/mysql.sock

[mysqld]
skip-grant-tables
port            = 3306
socket          = /tmp/mysql.sock
datadir = /www/server/data
default_storage_engine = InnoDB
performance_schema_max_table_instances = 400
table_definition_cache = 400
skip-external-locking
key_buffer_size = 32M
max_allowed_packet = 100G
4、重新启动MySQL数据库
[root@bp18425116f0cojd1vnz ~]# systemctl start mysql
5、以免密模式登录数据库
[root@bp18425116f0cojd1vnz ~]# mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.6.50-log Source distribution

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 
6、登录mysql数据库
mysql> use mysql;
Database changed
7、修改密码
mysql> update mysql.user set authentication_string=password('your_password') where user='root';
Query OK, 4 rows affected (0.01 sec)
Rows matched: 4  Changed: 4  Warnings: 0

your_password为自己想要替换的数据库密码

8、修改 /etc/my.cf

修改/etc/my.cf 文件 去除 skip-grant-tables

9、已修改之后的密码登录数据库
[root@bp18425116f0cojd1vnz ~]# mysql -uroot -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.6.50-log Source distribution

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> 

当连接器与客户端通过TCP进行三次握手连接成功之后,就会要求用户输入密码进行登录,当输入密码无误时,客户端与服务器建立连接成功之后,连接器就会去查询出改用户的权限然后存储到查询缓存中

2、查询缓存

当客户端的查询语句为select查询语句的时候,如若再查询缓存里面已经查询到了结果,就会直接把查询结果返回给客户端

3、解析器

在查询缓存并没有查询到结果之后,就会走到解析器,在解析器这儿,会做如下工作

1、词法分析

词法分析会根据客户端的SQL语句分析出各个关键词,简单地说就是把整个SQL拆分为一个个的单词,然后生茶一颗词法分析树

2、语法分析

在语法分析层面会根据上面生成的词法分析树判断SQL语句是否符合语法规则,如果不符合,就会进行相应的提示信息

mysql> select djglfdjg from user;
ERROR 1054 (42S22): Unknown column 'djglfdjg' in 'field list'

如若在解析器执行正确之后,就会去执行相应的SQL,走到执行器

4、SQL执行器

在执行器这个阶段,会进行SQL语句的执行,主要包括以下这几个部分

1、预处理阶段
  • 在开始执行的时候,预处理阶段你对这个表有没有执行查询的权限,如若没有,就会返回相应的错误
  • 检查查询的表或者字段是否存在,如若没有,也会返回相应的错误信息
2、优化器

在优化器阶段,优化器会对SQL的执行顺序,使用哪个索引进行优化,确定SQL的执行方案,在这里会生产explain的执行计划
比如这个语句

mysql> explain SELECT Host  FROM `user` where Host='localhost';
+----+-------------+-------+------+-------------------------+---------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys           | key     | key_len | ref   | rows | Extra                    |
+----+-------------+-------+------+-------------------------+---------+---------+-------+------+--------------------------+
|  1 | SIMPLE      | user  | ref  | PRIMARY,index_user_Host | PRIMARY | 180     | const |    3 | Using where; Using index |
+----+-------------+-------+------+-------------------------+---------+---------+-------+------+--------------------------+
1 row in set (0.00 sec)

通过explan执行语句可以查询到,在执行语句时,有以下结论

  • id=1 SELECT识别符,查询序号即为sql语句执行的顺序
  • select_type=SIMPLE 表示SQL查询语句走的是单表查询
  • table=user 输出的行所用的表
  • type=ref 显示了连接使用了哪种类别,有无使用索引,type扫描方式由快到慢
    system > const > eq_ref > ref > range > index > ALL
    system:系统表,少量数据,往往不需要进行磁盘IO
    const:常量连接
    eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描 ref:非主键非唯一索引等值扫描
    range:范围扫描
    index:索引树扫描
    all:全表扫描
  • possible_keys 表示查询语句可能会用到的索引,在这里有两个,PRIMARY表示为主键索引,index_user_Host为另一个索引
  • key 表示在查询语句时实际用到的索引,在这里为PRIMARY,那为什么这里只用到了PRIMARY这个索引呢,别急,后面会说到
  • key_len 表示使用的索引长度
  • ref 列显示使用哪个列或常数与key一起从表中选择行
  • rows 显示MySQL执行查询的行数,简单且重要,数值越大越不好,说明没有用好索引
  • Extra 该列包含MySQL解决查询的详细信息,
    Using index表示相应的select操作中使用了覆盖索引(covering index),避免访问了表的数据行,using where,表明索引被用来执行索引键值的查找
    Using where表明使用了where过滤
    Using join buffer使用了连接缓存
    Using temporary 使用了临时表保存中间结果,mysql在对查询结果排序时使用临时表。常见于排序order by和分组查询group by。
    group by一定要遵循所建索引的顺序与个数
    using filesort,using temporary,using index最为常见,出现前两种表示是需要优化的地方
    通过观察上面的执行语句,在查询时,有2个索引,但是只用到了PRIMARY这个索引,并没有用到index_user_Host,查询表所建立的索引
mysql> show  index from mysql.user;
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name        | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| user  |          0 | PRIMARY         |            1 | Host        | A         |        NULL |     NULL | NULL   |      | BTREE      |         |               |
| user  |          0 | PRIMARY         |            2 | User        | A         |           8 |     NULL | NULL   |      | BTREE      |         |               |
| user  |          1 | index_user_Host |            1 | Host        | A         |        NULL |     NULL | NULL   |      | BTREE      |         |               |
+-------+------------+-----------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

该表有建立3个索引,查询语句SELECT Host FROM user where Host='localhost';中查询字段为Host ,Extra为Using where; Using index表明用到了覆盖索引,也就是二级索引的 B+ 树的叶子节点的数据存储的是主键值,没有必要再索引检索磁盘IO来查询数据,也就是覆盖索引优化,所以并没有通过index_user_Host这个索引去检索数据

3、执行器

在执行器执行SQL语句会对权限进行校验,如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口与存储引擎层进行交互,执行SQL语句,并将结果返回个客户端。这里只是简单介绍,后面章节详细讲解。

 

 

3 MySQL存储引擎

 

InnoDB:支持事务,行锁、外键、非锁定读(读数据不上锁)、使用多版本并发控制(MVCC)提高并发性、隔离级别为repeatable并使用next-key locking 策略避免幻读、采用聚集索引。

需要注意:聚集索引和非聚集索引关键不在于索引和数据记录是否放在一个文件,而在于数据记录是否按照主键的顺序存放。

MyISAM:不支持事务、使用表锁、采用非聚集索引、一个表由MYD(存放数据记录)和MYI(存放索引)两个文件组成(frm表结构文件除外)。

NDB:是一个集群存储引擎,数据全部放在内存中。

Memory:表数据全部存放于内存,适合用于存储临时数据的临时表、使用哈希索引、只支持表锁,并发性差、不支持TEXT 和 BLOB类型,存储varchar时按照char方式存储,比较浪费内存。

mysql使用memory存储引擎作为临时表存放查询的中间结果(如分组、排序等),如果中间结果集超过Memory表的容量设置或者中间结果含有TEXT或BLOB类型字段,则会将中间结果转为MyISAM表存在磁盘中导致查询性能更慢。

Archive:其目标是提供高速插入和压缩存储功能、只支持insert和select操作、使用zlib对数据行压缩后存储、适用于存储归档数据如日志、使用行锁,但本身不是事务安全的。

Federated:本身不存放数据,而是指向远程mysql服务的一个表,实现对该表的操作。

Maria:开发时目的是用于替代MyISAM成为默认引擎的,提供和InnoDB基本一样的功能。

 

比对InnoDB和MyISAM

 

MyISAM 和 InnoDB 比较
1、MyISAM:是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM所改良。虽然性能极佳,但却有一个缺点:不支持事务处理(transaction)。它是存储记录和文件的标准方法。不是事务安全的,而且不支持外键,如果执行大量的select,insert MyISAM比较适合
2、InnoDB:支持事物安全的引擎,支持外键,行锁,事务控制是它最大的特点,在有大量的insert,update语句时,使用InnoDB比较合适,特别是针对多个并发和QPS较高的时候。

 

区别
表锁差异
MyISAM:
myisam只支持表级锁,用户在操作myisam表时,select,update,delete,insert语句都会给表自动加锁,如果加锁以后的表满足insert并发的情况下,可以在表的尾部插入新的数据。也可以通过lock table命令来锁表,这样操作主要是可以模仿事务,但是消耗非常大,一般只在实验演示中使用。

InnoDB :
Innodb支持事务和行级锁,是innodb的最大特色。
Innodb的行锁模式有以下几种:共享锁,排他锁,意向共享锁(表锁),意向排他锁(表锁),间隙锁。
注意:当语句没有使用索引,innodb不能确定操作的行,这个时候就使用的意向锁,也就是表锁

数据库文件差异
MyISAM :
myisam属于堆表
myisam在磁盘存储上有三个文件,每个文件名以表名开头,扩展名指出文件类型。
.frm 用于存储表的定义
.MYD 用于存放数据
.MYI 用于存放表索引
myisam表还支持三种不同的存储格式:
静态表(默认,但是注意数据末尾不能有空格,会被去掉)
动态表
压缩表

InnoDB :
innodb属于索引组织表
innodb有两种存储方式,共享表空间存储和多表空间存储
两种存储方式的表结构和myisam一样,以表名开头,扩展名是.frm。
如果使用共享表空间,那么所有表的数据文件和索引文件都保存在一个表空间里,一个表空间可以有多个文件,通过innodb_data_file_path和innodb_data_home_dir参数设置共享表空间的位置和名字,一般共享表空间的名字叫ibdata1-n。
如果使用多表空间,那么每个表都有一个表空间文件用于存储每个表的数据和索引,文件名以表名开头,以.ibd为扩展名。

索引差异
1、关于自动增长
myisam引擎的自动增长列必须是索引,如果是组合索引,自动增长可以不是第一列,他可以根据前面几列进行排序后递增。
innodb引擎的自动增长列必须是索引,如果是组合索引也必须是组合索引的第一列。

2、关于主键
myisam允许没有任何索引和主键的表存在,
myisam的索引都是保存行的地址。
innodb引擎如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键(用户不可见)
innodb的数据是主索引的一部分,附加索引保存的是主索引的值。

3、关于count()函数
myisam保存有表的总行数,如果select count() from table;会直接取出出该值
innodb没有保存表的总行数,如果使用select count() from table;就会遍历整个表,消耗相当大,但是在加了wehre 条件后,myisam和innodb处理的方式都一样。

4、全文索引
myisam支持 FULLTEXT类型的全文索引
innodb不支持FULLTEXT类型的全文索引,但是innodb可以使用sphinx插件支持全文索引,并且效果更好。(sphinx 是一个开源软件,提供多种语言的API接口,可以优化mysql的各种查询)

5、delete from table
使用这条命令时,innodb不会从新建立表,而是一条一条的删除数据,在innodb上如果要清空保存有大量数据的表,最 好不要使用这个命令。(推荐使用truncate table,不过需要用户有drop此表的权限)

6、索引保存位置
myisam的索引以表名+.MYI文件分别保存。
innodb的索引和数据一起保存在表空间里。

 

 

关于死锁:

什么是死锁?当两个事务都需要获得对方持有的排他锁才能完成事务,这样就导致了循环锁等待,也就是常见的死锁类型。

解决死锁的方法:
(1)按同一顺序访问对象:
如果所有并发事务按同一顺序访问对象,则发生死锁的可能性会降低。例如,如果两个并发事务获得 Supplier 表上的锁,然后获得 Part 表上的锁,则在其中一个事务完成之前,另一个事务被阻塞在 Supplier 表上。第一个事务提交或回滚后,第二个事务继续进行。不发生死锁。将存储过程用于所有的数据修改可以标准化访问对象的顺序。
(2)避免事务中的用户交互:
避免编写包含用户交互的事务,因为运行没有用户交互的批处理的速度要远远快于用户手动响应查询的速度,例如答复应用程序请求参数的提示。例如,如果事务正在等待用户输入,而用户去吃午餐了或者甚至回家过周末了,则用户将此事务挂起使之不能完成。这样将降低系统的吞吐量,因为事务持有的任何锁只有在事务提交或回滚时才会释放。即使不出现死锁的情况,访问同一资源的其它事务也会被阻塞,等待该事务完成。
(3)保持事务简短并在一个批处理中:
在同一数据库中并发执行多个需要长时间运行的事务时通常发生死锁。事务运行时间越长,其持有排它锁或更新锁的时间也就越长,从而堵塞了其它活动并可能导致死锁。
保持事务在一个批处理中,可以最小化事务的网络通信往返量,减少完成事务可能的延迟并释放锁。
(4)使用低隔离级别:
确定事务是否能在更低的隔离级别上运行。执行提交读允许事务读取另一个事务已读取(未修改)的数据,而不必等待第一个事务完成。使用较低的隔离级别(例如提交读)而不使用较高的隔离级别(例如可串行读)可以缩短持有共享锁的时间,从而降低了锁定争夺。
(5)使用绑定连接:
使用绑定连接使同一应用程序所打开的两个或多个连接可以相互合作。次级连接所获得的任何锁可以象由主连接获得的锁那样持有,反之亦然,因此不会相互阻塞。

 

 

参考:沛沛 http://www.zbpblog.com/blog-368.html

 

posted on 2022-10-17 17:32  一只阿木木  阅读(1032)  评论(0编辑  收藏  举报