连接器
连接器负责跟客户端建立连接、获取权限、维持和管理连接
mysql -h$ip -P$port -u$user -p
输完命令之后,你就需要在交互对话里面输入密码。密码也可以直接跟在 -p 后面(不要加空格)写在命令行中
mysql> show processlist;
其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接,
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 (28800秒)小时
show variables like 'wait_%'
分析器
mysql> select * from T where ID=10;
分析器包含词法分析,语法分析:
所谓词法分析将关键字识别成一个个token,
词法分析是识别一个个的单词,而语法分析就是在词法分析的基础上识别出程序的语法结构,也就是AST抽象语法树
例如:MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”
优化器
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序
执行器
调用 引擎接口读写数据,处理获取的数据
InnoDB 的索引模型
InnoDB 使用了 B+ 树索引模型,所以数据都是存储在 B+ 树中的
create table ltq(
id int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;
插入几条值,看下索引结构:
主键索引的叶子节点存的是整行数据,
B+tree的数据都存储在叶子节点,
非叶子节点放索引
每个数据页默认是16KB,把数据放在叶子节点,就可以存储更多的索引值,减少层级,加快查询速度
非主键索引的叶子节点内容是主键的值
每次新建一个索引都是建立一颗B+树
回表查询:
理解了数据结构就很容易知道啥叫回表,查询普通索引,普通****索引上存储的知识主键id,所以需要其他数据必须通过获取的主键id,到主索引去获取数据
索引覆盖:
覆盖索引是指,索引上的信息足够满足查询请求,不需要再回到主键索引上去取数据。
select id,k from ltq where k in (3,5);
通过普通索引已经可以满足要求,普通索引已经包含id,k 索引就不用回表了,特别适合联合索引
索引下推
CREATE TABLE `tuser` (
`id` int(11) NOT NULL,
`id_card` varchar(32) DEFAULT NULL,
`name` varchar(32) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`ismale` tinyint(1) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `id_card` (`id_card`),
KEY `name_age` (`name`,`age`)
) ENGINE=InnoDB
联合索引(name, age)
mysql> select * from tuser where name like '张%' and age=10 and ismale=1;
查询到ID3时,不用回表查对应数据,可以直接通过age字段过滤不满足条件的行,减少回表次数
MySQL 常见锁
全局锁
MySQL 提供了一个加全局读锁的方法,命令是 Flush tables with read lock
整个库处于只读状态,数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句,都会阻塞;
使用查看连接状态 show processlist,可以看到,连接阻塞了
使用查看连接状态 show processlist;
show processlist;
可以看到连接处在等待锁的状态,waiting fo global read lock;
解锁:unlock tables;
表级锁
一种是表锁,一种是元数据锁(meta data lock,MDL);
表锁的语法是 lock tables … read/write;
在某个线程 A 中执行 lock tables friend read, is_like write;
其他线程写 friend、读写 is_like 的语句都会被阻塞。
线程 A 只能执行读 friend、读写is_like的操作
另一类表级的锁是 MDL**(metadata lock)**
当对一个表做增删改查操作的时候,加 MDL 读锁;当要对表做结构变更操作的时候,加 MDL 写锁
-
读锁之间不互斥,可以有多个线程同时对一张表增删改查
-
读写锁之间、写锁之间是互斥的
session A 先启动,会对表 t 加一个 MDL 读锁
session B 需要的也是 MDL 读锁(读锁不互斥)
session C 添加age列,这个时候session A持有读锁还没提交,所以没释放锁
session C 会被 blocked(读写互斥),
读锁不释放,后面sessionD的查询语句也会被阻塞
下面是操作结果图
行锁
MySQL 的行锁是在引擎层由各个引擎自己实现的,MyISAM 引擎就不支持行锁
行锁就是针对数据表中行记录的锁
begin;
开启事务;
执行 update 语句给对应行加上行锁(持有行锁)
当其他线程连接对同一行更新时,就被阻塞,等待行锁
行锁必须等待事务提交后才释放
行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放
如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
这样可以最大程度的提高并发度
当你在一个大事务中,要尽可能的把更新语句往后放,这样就可以晚开启行锁,事务提交,尽快释放行锁
order by
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`city` varchar(16) NOT NULL,
`name` varchar(16) NOT NULL,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `city` (`city`)
) ENGINE=InnoDB;
select city,name,age from t where city='杭州' order by name limit 1000 ;
全字段排序
Extra 这个字段中的“Using filesort”需要排序,MySQL 会给每个线程分配一块内存用于排序,称为 sort_buffer,sort_buffer是在server层
-
sort_buffer是在server层,执行器查看表定义,发现name、city、age字段的长度之和小于max_length_for_sort_data
-
初始化 sort_buffer,确定放入 name、city、age 这三个字段;
-
从索引 city 找到第一个满足 city='杭州’条件的主键 id,也就是图中的 ID_X;
-
到主键 id 索引取出整行,取 name、city、age 三个字段的值,存入 sort_buffer 中;
-
从索引 city 取下一个记录的主键 id;重复步骤 3、4 直到 city 的值不满足查询条件为止,对应的主键 id 也就是图中的 ID_Y;
-
对 sort_buffer 中的数据按照字段 name 做快速排序;按照排序结果取前 1000 行返回给客户端。
示意图:
rowid 排序
如果排序的行数据的长度超过 max_length_for_sort_data;
show variables like '%max_length_for_sort_data%'
修改
SET max_length_for_sort_data = 16;
-
执行器查看表定义,发现name、city、age字段的长度之和超过max_length_for_sort_data,所以初始化sort_buffer的时候只放入id和name字段。
-
执行器调用存储引擎的读数据接口,从索引 city 找到第一个满足 city='杭州’条件的主键 id,也就是图中的 ID_X;
-
到主键 id 索引取出整行,取 name、id 这两个字段,存入 sort_buffer 中;
-
从索引 city 取下一个记录的主键 id;
-
重复步骤 3、4 直到不满足 city='杭州’条件为止,也就是图中的 ID_Y;
-
对 sort_buffer 中的数据按照字段 name 进行排序;
-
遍历排序结果,取前 1000 行,并按照 id 的值回到原表中取出 city、name 和 age 三个字段返回给客户端。
排序过程:
-
结果集”是一个逻辑概念,
实际上 MySQL 服务端从排序后的 sort_buffer 中依次取出 id,
然后到原表查到 city、name 和 age 这三个字段的结果,服务端再耗费内存存储结果,是直接返回给客户端的
MySQL 之所以需要生成临时表,并且在临时表上做排序操作,其原因是原来的数据都是无序的
alter table t add index city_user(city, name);
-
加了复合索引之后,数据就是有序的,会先按第一个字段排序,再按第二个字段排序
-
从索引 (city,name) 找到第一个满足 city='杭州’条件的主键 id;
-
到主键 id 索引取出整行,取 name、city、age 三个字段的值,作为结果集的一部分直接返回;
-
从索引 (city,name) 取下一个记录主键 id; 重复步骤 2、3,直到查到第 1000 条记录,或者是不满足 city='杭州’条件时循环结束。
-
-
未加复合索引的,使用了排序
添加(city,name)复合索引后,直接从索引中获取,不用使用排序:
随机查询
随机排序取前 3 个
mysql> CREATE TABLE `words` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`word` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB;
mysql> select word from words order by rand() limit 3;
可以看到 Extra 字段显示 Using temporary,表示的是需要使用临时表;Using filesort,表示的是需要执行排序操作。
- 需要使用memory引擎创建临时表 。表里有两个字段,第一个字段是 double 类型,放随机生成数字,第二个字段是 varchar(64) 类型,放word值
- 从 words 表中,按主键顺序取出所有的 word 值。对于每一个 word 值,调用 rand() 函数生成一个大于 0 小于 1 的随机小数,并把这个随机小数和 word 分别存入临时表
- 初始化 sort_buffer。sort_buffer 中有两个字段,一个是 double 类型,另一个是整型。从内存临时表中一行一行地取出随机值和主键
- 分别存入 sort_buffer 中的两个字段里。在 sort_buffer 中根据 随机值 进行排序。排序完成后,取出前三个结果的位置信息,依次到内存临时表中取出 word 值,返回给客户端
随机查询好的方案:
-
下面是比较好的方案
随机查询好的方案:
-
先查询满足的所有行count(*) 记为count;
-
随机生成三个值 a,b,c;
-
再执行三个 limit Y, 1 语句得到三行数据。
mysql> select count() into C from t; Integer a = Math.floor(count rand()); Integer b = Math.floor(count* rand()); Integer c = Math.floor(count* rand()); select * from t limit a; 1;select * from t limit b,1; select * from t limit c,1;
-
MySQL 中有两个 kill 命令:
- 一个是 kill query + 线程 id;
- 一个是 kill connection + 线程 id;
当你发现有连接死锁,或者大事务占用内存
show processlist;
查看连接状态,通过id关闭连接