客户端连接
支持接口:支持的客户端连接,例如 C、Java、PHP 等语言来连接 MySQL 数据库
第一层:网络连接层
连接池:管理、缓冲用户的连接,线程处理等需要缓存的需求。
例如:当客户端发送一个请求连接,会从连接池中获取一个连接进行使用。
第二层:核心服务层
管理服务和工具:系统的管理和控制工具,例如备份恢复、复制、集群等。
SQL 接口:接受 SQL 命令,并且返回查询结果。
查询解析器:验证和解析 SQL 命令,例如过滤条件、语法结构等。
查询优化器:在执行查询之前,使用默认的一套优化机制进行优化 sql 语句缓存:如果缓存当中有想查询的数据,则直接将缓存中的数据返回。没有的话再重新查询!
第三层:存储引擎层
插件式存储引擎:管理和操作数据的一种机制,包括(存储数据、如何更新、查询数据等)
第四层:系统文件层
文件系统:配置文件、数据文件、日志文件、错误文件、二进制文件等等的保存
★ MySQL 数据库使用不同的机制存取表文件 , 机制的差别在于不同的存储方式、索引技巧、锁定水平以及广泛的不同的功能和能力,在 MySQL 中 , 将这些不同的技术及配套的功能称为存储引擎.
★ 在关系型数据库中数据的存储是以表的形式存进行储的,所以存储引擎也可以称为表类型(即存储和操作此表的类型)。
★ Oracle , SqlServer 等数据库只有一种存储引擎 , 而 MySQL 针对不同的需求, 配置 MySQL 的不同的存储引擎 , 就会让数据库采取了不同的处理数据的方式和扩展功能。
通过选择不同的引擎 ,能够获取最佳的方案 , 也能够获得额外的速度或者功能,提高程序的整体效果。所以了解引擎的特性 , 才能贴合我们的需求 , 更好的发挥数据库的性能。
MySQL5.7 支持的引擎包括:InnoDB、MyISAM、MEMORY、Archive、Federate、CSV、BLACKHOLE 等
其中较为常用的有三种引擎:InnoDB、MyISAM、MEMORY
访问快,不支持事务和外键。表结构保存在.frm 文件中,表数据保存在.MYD 文件中,索引保存在.MYI 文件中。
支持事务 ,占用磁盘空间大 ,支持并发控制。表结构保存在.frm 文件中,如果是共享表空间,数据和索引保存在 innodb_data_home_dir 和 innodb_data_file_path 定义的表空间中,可以是多个文件。如果是多表空间存储,每个表的数据和索引单独保存在 .ibd 中。
内存存储 , 速度快 ,不安全 ,适合小量快速访问的数据。表结构保存在.frm 中。
标准语法
SHOW ENGINES;
查出的表含义
- support ∶指服务器是否支持该存储引擎
-transactions ∶指存储引擎是否支持事务
- XA ∶指存储引擎是否支持分布式事务处理
- Savepoints ∶指存储引擎是否支持保存点
查询某个数据库中所有数据表的引擎
SHOW TABLE STATUS FROM 数据库名称;
查询某个数据库中某个数据表的引擎
SHOW TABLE STATUS FROM db14 WHERE NAME ='数据表名称';
练 习
查询db14数据库所有表的存储引擎
SHOW TABLE STATUS FROM db14;
查询db14数据库中count表的存储引擎
SHOW TABLE STATUS FROM db14 WHERE NAME ='count';
创建数据表,并指定引擎
CREATE TABLE 表名(
列名,数据类型,
...
)ENGINE = 引擎名称;
修改表的存储引擎
ALTER TABLE 表名 ENGINE = 引擎名称;
练 习
创建engine_test表,指定存储引擎为MYISAM
CREATE TABLE engine_test(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10)
)ENGINE = MYISAM;
查询该表引擎
SHOW TABLE STATUS FROM db14 WHERE NAME ='engine_test';
修改engine_test表的引擎为InnoDB
ALTER TABLE 表名 ENGINE = 引擎名称;
查询该表引擎
SHOW TABLE STATUS FROM db14 WHERE NAME ='engine_test';
MyISAM :由于 MyISAM 不支持事务、不支持外键、支持全文检索和表级锁定,读写相互阻塞,读取速度快,节约资源,所以如果应用是以查询操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那么选择这个存储引擎是非常合适的。
InnoDB : 是 MySQL 的默认存储引擎, 由于 InnoDB 支持事务、支持外键、行级锁定 ,支持所有辅助索引(5.5.5 后不支持全文检索),高缓存,所以用于对事务的完整性有比较高的要求,在并发条件下要求数据的一致性,读写频繁的操作,那么 InnoDB 存储引擎是比较合适的选择,比如 BBS、计费系统、充值转账等
MEMORY:将所有数据保存在 RAM 中,在需要快速定位记录和其他类似数据环境下,可以提供更快的访问。MEMORY 的缺陷就是对表的大小有限制,太大的表无法缓存在内存中,其次是要确保表的数据可以恢复,数据库异常终止后表中的数据是可以恢复的。MEMORY 表通常用于更新不太频繁的小表,用以快速得到访问结果。
总结:针对不同的需求场景,来选择最适合的存储引擎即可!如果不确定、则使用数据库默认的存储引擎!
这里的索引和我们学ArrayList的索引差不多。有索引的目的就是为了通过索引快速查询数据。MySQL 数据库中的索引:是帮助 MySQL 高效获取数据的一种数据结构!所以,索引的本质就是数据结构。 在表数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构上实现高级查找算法,这种数据结构就是索引。一张数据表,用于保存数据。 一个索引配置文件,用于保存索引,每个索引都去指向了某一个数据(表格演示)
举例,无索引和有索引的查找原理
功能分类
普通索引: 最基本的索引,它没有任何限制。
唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值组合必须唯一。
主键索引:一种特殊的唯一索引,不允许有空值。一般在建表时同时创建主键索引。
组合索引:顾名思义,就是将单列索引进行组合。
外键索引:只有 InnoDB 引擎支持外键索引,用来保证数据的一致性、完整性和实现级联操作。
全文索引:快速匹配全部文档的方式。InnoDB 引擎 5.6 版本后才支持全文索引。MEMORY 引擎不支持。
结构分类
B+Tree 索引 :MySQL 使用最频繁的一个索引数据结构,是 InnoDB 和 MyISAM 存储引擎默认的索引类型。
Hash 索引 : MySQL 中 Memory 存储引擎默认支持的索引类型。
创建索引的标准语法(一)
CREATE [UNIQUE|FULLTEXT] INDEX 索引名称
[USING 索引类型] --默认是B+TREE
ON 表名(列名...);
创建索引的标准语法(二)
普 通 索 引
ALTER TABLE 表名 ADD INDEX 索引名称(列名);
组 合 索 引
ALTER TABLE 表名 ADD INDEX 索引名称(列名1,列名2,...);
主 键 索 引
ALTER TABLE 表名 ADD PRIMARY KEY(主键名称);
外 键 索 引(添加外键约束就是外键索引)
ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY(本表外键列名) REFERENCES 主表名(主键列名);
唯 一 索 引
ALTER TABLE 表名 ADD UNIQUE 索引名称(列名);
全 文 索 引(MySQL只支持文本类型)
ALTER TABLE 表名 ADD FULLTEXT 索引名称(列表);
查 看 索 引
SHOW INDEX FROM 表名;
删 除 索 引
DROP INDEX 索引名称 ON 表名;
数 据 准 备
CREATE DATABASE db16;
USE db16;
--创建student表
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
age INT,
score INT
);
--添加数据
INSERT INTO student VALUES(NULL,'张三',23,98),(NULL,'李四',24,95),(NULL,'王五',25,96),(NULL,'赵六',26,94),(NULL,'周七',27,99);
--为student表中姓名列创建一个普通索引
CREATE INDEX idx_name ON student(NAME);
--为student表中年龄列创建一个唯一索引
CREATE UNIQUE INDEX idx_age ON student(age);
--为student表中name列添加全文索引
ALTER TABLE student ADD FULLTEXT idx_fulltest_name(name);
--查看student表中的索引
SHOW INDEX FROM student;
--创建商品表
CREATE TABLE product(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(10),
price INT
);
-----------------------------------------------------------
--定义存储函数
DELIMITER $
CREATE FUNCTION rand_string()
RETURNS VARCHAR(255)
BEGIN
DECLARE big_str VARCHAR(100) DEFAULT
'abcdefghijk1mnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVwXYZ';
DECLARE small_str VARCHAR(255) DEFAULT'';
DECLARE i INT DEFAULT 1;
WHILE i<= 10 DO
SET small_str =CONCAT(small_str,SUBSTRING(big_str,FLOOR(1+RAND()*52),1));
SET i=i+1;
END wHILE;
RETURN small_str;
END$
DELIMITER;
----------------------------------------------------------------------
--定义存储过程,添加100万条数据到product表中
DELIMITER $
CREATE PROCEDURE pro_test()
BEGIN
DECLARE num INT DEFAULT 1;
WHILE num <=1000000 DO
INSERT INTO product VALUES(NULL,rand_string(),num);
SET num = num +1;
END WHILE;
END$
DELIMITER;
-------------------------------------------------------------------
--调用存储过程
CALL pro_test();
--查询总记录条数
SELECT COUNT(*) FROM product;
-------------------------------------------------------------------
--查询product表的索引
SHOW INDEX FROM product;
--查询name为VhbGrHjYrO的数据(0.154S)
SELECT * FROM product WHERE NAME ='VhbGrHjYrO';
--通过id查询VhbGrHjYrO的数据(0.016S)
SELECT * FROM product WHERE id=612720;
--为name列添加索引
ALTER TABLE product ADD INDEX idx_name(NAME);
--查询name为VhbGrHjYrO的数据(0.019S)
SELECT * FROM product WHERE NAME ='VhbGrHjYrO';
--查询价格为800-1000之间的所有数据(0.151S)
SELECT * FROM product WHERE price BETWEEN 800 AND 1000;
----查询价格为800-1000之间的所有数据,降序排序(0.177S)
SELECT * FROM product WHERE price BETWEEN 800 AND 1000 ORDER BY price DESC;
--为price添加索引
ALTER TABLE product ADD INDEX idx_price(price);
--查询价格为800-1000之间的所有数据(0.017S)
SELECT * FROM product WHERE price BETWEEN 800 AND 1000;
----查询价格为800-1000之间的所有数据,降序排序(0.023)
SELECT * FROM product WHERE price BETWEEN 800 AND 1000 ORDER BY price DESC;
BTree 结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述 BTree,首先定义一条记录为一个二元组[key, data] ,key 为记录的键值,对应表中的主键值,data 为一行记录中除主键外的数据。对于不同的记录,key 值互不相同。BTree 中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3 阶的 BTree:
根据图中结构显示,每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为 17 和 35,P1 指针指向的子树的数据范围为小于17,P2 指针指向的子树的数据范围为 17~35,P3 指针指向的子树的数据范围为大于 35。
B+Tree 是在 BTree 基础上的一种优化,使其更适合实现外存储索引结构,InnoDB 存储引擎就是用 B+Tree实现其索引结构。从上一节中的 BTree 结构图中可以看到每个节点中不仅包含数据的 key 值,还有 data 值。而每一个页的存储空间是有限的,如果 data 数据较大时将会导致每个节点(即一个页)能存储的 key 的数量很小,当存储的数据量很大时同样会导致 B-Tree 的深度较大,增大查询时的磁盘 I/O 次数,进而影响查询效率。在 B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储 key 值信息,这样可以大大加大每个节点存储的 key值数量,降低 B+Tree 的高度。
将上一节中的 BTree 优化,由于 B+Tree 的非叶子节点只存储键值信息,假设每个磁盘块能存储 4 个键值 及指针信息,则变成 B+Tree 后其结构如下图所示:
通常在 B+Tree 上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对 B+Tree 进行两种查找运算:
实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree 的高度一般都在 2~4 层。MySQL 的 InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要 1~3 次磁盘 I/O操作
★什么是联合索引:
对多个字段同时建立的索引(是有顺序的,ABC,ACB是完全不同的两种联合索引。)
★为什么要有联合索引:
以联合索引(a,b,c)为例,建立这样的索引相当于建立了索引a、ab、abc三个索引。一个索引顶三个索引当然是好事,毕竟每多一个索引,都会增加写操作的开销和磁盘空间的开销。
同样的有联合索引(a,b,c),如果有如下的sql: SELECT a,b,c FROM TABLE WHERE a=xxx AND b = xxx。那么MySQL可以直接通过遍历索引取得数据,而无需读表,这减少了很多的随机io操作。减少IO操作,特别的随机IO其实是DBA主要的优化策略。
★联合索引的特点:
在 mysql 建立联合索引时会遵循最左前缀匹配的原则,即最左优先,在检索数据时从联合索引的最左边开始匹配。对列 name 列、address 和列 phone 列建一个联合索引
1 对user表中的name 列,address 和列 phone 列建一个联合索引
ALTER TABLE user ADD INDEX ceshi(name,address,phone);
2 联合索引 index_three 实际建立了(name)、(name,address)、(name,address,phone)三个索引。所以下面 的三个 SQL 语句都可以 命中索引(通过索引查询)。
SELECT * FROM user WHERE address ='北京' AND phone='12345' AND name='张三';
SELECT * FROM user WHERE address ='北京' AND name='张三';
SELECT * FROM user WHERE name='张三';
3 Mysql 的优化器会帮助我们调整 where 条件中的顺序,以匹配我们建立的索引。 (但联合索引中最左边的列必须包含在条件查询中)
SELECT * FROM user WHERE address ='北京' AND phone='12345' ;这种情况就不会命中索引。
可以遵循一些已有的原则,创建索引的时候请尽量考虑符合这些原则,便于提升索引的使用效率,更高效的使用索引。
对查询频次较高,且数据量比较大的表建立索引。使用唯一索引,区分度越高,使用索引的效率越高。 索引字段的选择,最佳候选列应当从 where 子句的条件中提取,如果 where 子句中的组合比较多,那么应当挑选最常用、过滤效果最好的列的组合。
使用短索引,索引创建之后也是使用硬盘来存储的,因此提升索引访问的 I/O 效率,也可以提升总体的访问效率。假如构成索引的字段总长度比较短,那么在给定大小的存储块内可以存储更多的索引值,相应的 可以有效的提升 MySQL 访问索引的 I/O 效率。
索引可以有效的提升查询数据的效率,但索引数量不是多多益善,索引越多,维护索引的代价自然也就水 涨船高。对于插入、更新、删除等 DML 操作比较频繁的表来说,索引过多,会引入相当高的维护代价,降低 DML 操作的效率,增加相应操作的时间消耗。另外索引过多的话,MySQL 也会犯选择困难病,虽然最终仍然会找到一个可用的索引,但无疑提高了选择的代价。
之前我们学习过多线程,多线程当中如果想保证数据的准确性是同步实现。同步就相当于是加锁。加了锁以后有什么好处呢?当一个线程真正在操作数据的时候,其他线程只能等待。 当一个线程执行完毕后,释放锁。其他线程才能进行操作!
那么我们的 MySQL 数据库中的锁的功能也是类似的。在我们学习事务的时候,讲解过事务的隔离性,可能会出现脏读、不可重复读、幻读的问题,当时我们的解决方式是通过修改事务的隔离级别来控制,但是数据库的隔离级别呢我们并不推荐修改。所以,锁的作用也可以解决掉之前的问题!
在数据库中,数据是一种供许多用户共享访问的资源,如何保证数据并发访问的一致性、有效性,是所有数据库必须解决的一个问题,MySQL 由于自身架构的特点,在不同的存储引擎中,都设计了面对特定场景的锁定机制,所以引擎的差别,导致锁机制也是有很大差别的。
锁机制 : 数据库为了保证数据的一致性,而使用各种共享的资源在被并发访问时变得有序所设计的一种规则。
按操作分类:
共享锁:也叫读锁。针对同一份数据,多个事务读取操作可以同时加锁而不互相影响 ,但是不能修改数据记录。
排他锁:也叫写锁。当前的操作没有完成前,会阻断其他操作的读取和写入
按粒度分类:
表级锁:操作时,会锁定整个表。开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低。偏向于 MyISAM 存储引擎!
行级锁:操作时,会锁定当前操作行。开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高。偏向于 InnoDB 存储引擎!
页级锁:锁的粒度、发生冲突的概率和加锁的开销介于表锁和行锁之间,会出现死锁,并发性能一般。
按使用方式分类:
悲观锁:每次查询数据时都认为别人会修改,很悲观,所以查询时加锁。
乐观锁:每次查询数据时都认为别人不会修改,很乐观,但是更新时会判断一下在此期间别人有没有去更新这个数据
标 准 语 法
SELECT语句 LOCK IN SHARE MODE;
数 据 准 备
CREATE DATABASE db18;
USE db18;
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
score INT
);
INSERT INTO student VALUES (NULL,'张三',23,99),(NULL,'李四',24,95),(NULL,'王五',25,98),(NULL,'赵六',26,97);
/*
排他锁:加锁的数据,不能被其他事务加锁查询或修改
*/
窗口一
--开启事务
START TRANSACTION;
--查询id为1的数据记录。加入共享锁
SELECT * FROM student WHERE id=1 LOCK IN SHARE MODE;
--提交事务
COMMIT;
窗口二
--开启事务
START TRANSACTION;
--查询id为1的数据类型(普通查询,可以查询)
SELECT * FROM student WHERE id=1
--查询id为1的数据类型,并加入共享锁(可以查询。共享锁与共享锁兼容)
SELECT * FROM student WHERE score=99 LOCK IN SHARE MODE;
--修改id为1的姓名为张三三(不能修改,会出现锁的情况。只有窗口一提交事务后,才会修改成功)
UPDATE student SET NAME='张三三' WHERE id =1;
--修改id为2的姓名为李四四(修改成功,INNODB引擎默认是行锁)
UPDATE student SET NAME='张四四' WHERE id =2;
--修改id为3的姓名为王五五(注意:Inoodb引擎如果不采用带索引的列。则会提升为表锁)
UPDATE student SET NAME='张五五' WHERE id =3;
--提交事务
COMMIT;
标 准 语 法
SELECT语句 FOR UPDATE;
/*
排他锁:加锁的数据,不能被其他事务加锁查询或修改
*/
窗口一
--开启事务
START TRANSACTION;
--查询id为1的数据记录,并加入排他锁
SELECT * FROM student WHERE id=1 FOR UPDATE;
--提交事务
COMMIT;
窗口二
--开启事务
START TRANSACTION;
--查询id为1的数据记录(普通查询没有问腿)
SELECT * FROM student WHERE id=1;
--查询id为1的数据记录并加入共享锁(不能查询)
SELECT * FROM student WHERE id=1 LOCK IN SHARE MODE;
--查询id为1的数据记录并加入排他锁(不能查询)
SELECT * FROM student WHERE id=1 FOR UPDATE;
--修改数据把张三三改回张三。(不能修改,只有窗口一提交后才可以)
UPDATE student SET NAME='张三' WHERE id=1;
--提交事务
COMMIT;
PS:锁的兼容性
1.读 锁
定义:
所有的连接只能读取数据,不能修改。
加锁语法:
LOCK TABLE 表名 READ;
解锁语法(将当前会话所有的表进行解锁):
UNLOCK TABLES;
2.写锁
定义:
其他连接不能查询和修改数据
加锁语法:
LOCK TABLE 表名 WRITE;
解锁语法(将当前会话所有的表进行解锁):
UNLOCK TABLES;
数 据 准 备
CREATE TABLE product(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
price INT
)ENGINE = MYISAM;
INSERT INTO product VALUES (NULL,'华为手机',4999),(NULL,'小米手机',2999),(NULL,'苹果',8999),(NULL,'中兴',1999);
/*
读锁:所有连接只能读取数据,不能修改
*/
窗口一
--为product表加入读锁
LOCK TABLE product READ;
--查询product表(查询成功)
SELECT * FROM product;
--修改华为手机价格为5999(修改失败)
UPDATE product SET price = 5999 WHERE id =1;
--解锁
UNLOCK TABLES;
窗口二
--查询product表(查询成功)
SELECT * FROM product;
--修改华为手机价格为5999(修改失败,窗口一解锁后才能修改成功)
UPDATE product SET price = 5999 WHERE id =1;
/*
写锁:其他连接不能查询和修改数据
*/
窗口一
--为product表添加写锁
LOCK TABLE product WRITE;
--查询product表(查询成功)
SELECT * FROM product;
--修改小米手机价格为3999(修改成功)
UPDATE product SET price = 3999 WHERE id =2;
--解锁
UNLOCK TABLES;
窗口二
--查询product表(不能查询。只有窗口一解锁后才能查询成功)
SELECT * FROM product;
--修改小米手机价格为2999(不能修改。只有窗口一解锁后才能查询成功)
UPDATE product SET price = 1 WHERE id =2;
(1)悲观锁的概念
就是很悲观,它对于数据被外界修改的操作持保守态度,认为数据随时会修改。整个数据处理中需要将数据加锁。悲观锁一般都是依靠关系型数据库提供的锁机制。我们之前所学的行锁,表锁不论是读写锁都是悲观锁。
(2)乐观锁的概念
就是很乐观,每次自己操作数据的时候认为没有人会来修改它,所以不去加锁。但是在更新的时候会去判断在此期间数据有没有被修改。需要用户自己去实现,不会发生并发抢占资源,只有在提交操作的时候检查是否违反数据完整性
(3)悲观锁和乐观锁使用前提
对于读的操作远多于写的操作的时候,这时候一个更新操作加锁会阻塞所有的读取操作,降低了吞吐量。最后还要释放锁,锁是需要一些开销的,这时候可以选择乐观锁。如果是读写比例差距不是非常大或者系统没有响应不及时,吞吐量瓶颈的问题,那就不要去使用乐观锁,它增加了复杂度,也带来了业务额外的风险。这时候可以选择悲观锁。
(4)乐观锁的实现方式
版本号:给数据表中添加一个 version 列,每次更新后都将这个列的值加 1。读取数据时,将版本号读取出来,在执行更新的时候,比较版本号。如果相同则执行更新,如果不相同,说明此条数据已经发生了变化。用户自行根据这个通知来决定怎么处理,比如重新开始一遍,或者放弃本次更新。
时间戳 :和版本号方式基本一样,给数据表中添加一个列,名称无所谓,数据类型需要是 timestamp
每次更新后都将最新时间插入到此列。读取数据时,将时间读取出来,在执行更新的时候,比较时间。如果相同则执行更新,如果不相同,说明此条数据已经发生了变化。
表锁和行锁
行锁:锁的粒度更细,加行锁的性能损耗较大。并发处理能力较高。InnoDB 引擎默认支持!
表锁:锁的粒度较粗,加表锁的性能损耗较小。并发处理能力较低。InnoDB、MyISAM 引擎支持!
InnoDB 锁优化建议