MySQL使用原则:
- 自己扛住的自己扛,扛不住的使用中间件,比如:Redis;
- 最佳的单表存储量是千万级别,超过这个级别采取分表分布的方式来存储表。
MySQL体系架构
MySQL语句在数据库的执行过程:
- 经过parser进行词法解析和语法解析;
- 解析之后经过优化器生成执行计划,MySQL不缓存执行计划(也叫做硬解析);
- 执行计划进入执行器执行,到存储引擎中找数据;
- 返回数据。
MySQL包括核心层(mysqld)和存储引擎层,前者在内存和CPU中执行,后者在硬盘中,指的是不同的存储方法,最主要的两种是InnoDB和MyISAM
MySQL一次读取数据为16K,每个16K叫做page(页),
InnoDB
包括主键索引和辅助索引(又叫K值索引,二级索引),主键索引的叶子节点存放的就是记录,辅助键索引叶子结点存放的是索引字段和主键,通过索引字段查找对应的记录,根据该记录中的主键去主键索引找对应的记录。
建表时,如果没有声明主键,InnoDB会通过第一个非空且唯一的字段建立主索引,没有的话,会建立隐藏列(6字节)。
索引是一颗B+树,所有的叶子节点是一个双向链表,在插入数据维护索引时,可能会出现页分裂的情况。
根据辅助索引查询数据时,如果需要查询除主键索引外的数据,那么需要回表(辅助索引到主键索引的过程),但是如果只查找主键,就不需要回表。
联合索引
多个字段联合建立索引。
最左前缀原则
当你定义了联合索引时,在执行查找时,只有where字句出现的字段与联合索引出现的顺序一致,才能使用上索引。
支持事务。
表在存储引擎中被存放到两个文件,.idb文件存放的实际数据,.frm文件存放的是表的定义,由于系统中文件大小有限制,因此表存储的数据量有限制。
innodb buffer pool - 数据和索引都能缓存,MyISAM只能缓存索引。
MySQL查询数据时取表中16K的数据放到Innodb buffer pool做查询,满足查询条件的数据留下,继续扫描直到扫描了全表,同时,也存储了索引。
相关参数:
innodb_buffer_pool_size - innodb buffer pool在内存里的大小;
innodb_buffer_pool_instance - innodb buffer pool被分为几块,一般设置成16或者32。
innodb buffer pool使用LRU(Least Recent Used,最近最少使用)算法存储缓存在内存中的数据,默认页的大小是16K。
查询机制:
- 如果要查询到数据所在页不在Buffer Pool中,把该页从磁盘加载到Buffer Pool中的缓存页时,将该缓存页包装成节点塞到链表的头部;
- 如果该页在Buffer pool中,则直接把该页对应的LRU链表节点移动到链表的头部。
- LRU链的前半部分称为热端,后半部分称为冷端。
- 缓存页直接放在热端带来的问题是,访问极少使用的表后,对应的页会放置在热端,造成原本频繁访问的数据被淘汰,后期再访问时存在大量磁盘IO,因此为了优化这一问题,在LRU中设置了midpoint,默认情况下新缓存页第一次插入到LRU列表的5/8处,再次查询时再放到LRU链表头部。
update机制
事务:对数据做修改,以commit或者rollback结束的过程。
假设当前有个用户对某条数据进行更新,执行如下语句:update emp set name=JACK where empno=7369;
,随后又来一个用户读取同一条数据,select * form emp where empno=7369;
,数据库默认不执行commit,其流程如下:
- 读线程io_read_thread从存储引擎的*.idb文件中找到该行数据,并读取到innodb buffer pool中,并将修改前的数据写到UNDO中;
- 写线程io_write_thread对innodb buffer pool中的数据进行修改,此时,事务未进行commit时,该条数据在innodb buffer pool和存储引擎中的数据会不一致,在innodb buffer pool中的是否在事务中字段会存储是,UNDO字段中会存放该条数据前镜像的地址**;
- 后一个用户此时读数据时,读线程在innodb buffer pool中找到该条数据后,根据事务字段判断该条数据处于事务未提交状态,然后根据UNDO字段找到数据存储在ibdata1文件(存储被修改前的数据)中的地址,并去读取。
UNDO文件(保证一致性)的作用:
- 事务未提交时作一致性读取;
- 事务回滚后从UNDO中找回更新前的数据。
数据库软件设计的原则
- 内存读优于磁盘读;
- 日志先行(先记再改)。
log buffer
- log buffer在内存中,记录下对数据库的update、delete和insert操作,由log thread(只有1个)执行;
- commit后,log buffer会写到磁盘中,存在ib_logfile0和ib_logfile1文件中,这两个文件称作redo log,当两个文件都写满之后,接下来数据库只能进行读,无法执行其他操作,这时的数据库状态叫做hung住。
- 写到磁盘的条件:1)达到1/2满;2)超过1M大小;3)commit(相关的参数:inndb_flush_log_at_trx_commit,这个参数=0时,表示log buffer每秒落一次盘;=1时表示每次commit就落盘,默认是1;);满足上述三个条件中的任意一个log buffer都会写入到磁盘中;
解决系统因为redo日志无法覆盖造成hung住的解决方案: - 调整redo日志大小;
- 调整redo日志文件数量。
MySQL启动时会进行实例恢复,即重做redo日志。
redo日志不能归档,即ib_logfile0和ib_logfile1文件写满后无法复制到其他文件中。
change buffer
- 在innodb buffer pool中,用来缓存用户在innodb buffer pool对数据进行修改且未提交的数据;
- 目的是减少落盘时磁头在不同盘块间的移动次数(即随机访问的次数);
- 不适合马上写马上读的操作;
- 唯一索引不能使用change buffer。
表连接
- 驱动表:表连接时,需要全表扫描的表,让非驱动表走索引。
栗子:select * from t1 join t2 on(t1.a=t2.1); //其中t1是驱动表
- Index Nested-Loop Join(Index NLJ,嵌套循环连接):非驱动表走索引,优化的方向;
- Simple Nested-Loop Join(Simple NLJ):非驱动表不走索引,
select * from t1 straight_join t2 on(t1.a=t2.b);
- Block Nested-Loop Join(BNLJ):即当连接的表都不存在索引时,在作连接时,会将驱动表整块表或者分块放进join buffer中,由于驱动表存放在内存中,因此在作全表扫描时,能够加快表的连接;
- Batched Key Access:Index Nested-Loop Join 和 MRR的结合;
表连接的方式使用选择:Index NLJ -> BNLJ -> Simple NLJ,除了Simple NLJ,其他的表连接方式都会用到join buffer; - 辅助索引范围扫描的优化方式:Multi rang read(MRR),存储在分配给用户的buffer中,即在对辅助索引进行范围查询时,每查到一个数据不直接进行回表,而是将所有满足条件的数据放到MRR buffer中,按照主键索引进行排序,然后再根据排序的结果返回到主键索引中进行查找。
SQL语句 -- 考题
- select length("开心生活") - 返回存储字符串的字节数;
- select char_length("开心生活") - 返回字符串的字符数,与本地客户端的字节编码方式(character_set_client = utf8 or latin1)有关;
- 每个用户都会有自己的join、sort、multi range的buffer;
- select concat和select concat_ws的区别:前者将所有字符串连接在一块,后者第一个参数是其它参数的分隔符,分隔符的位置放在要连接的两个字符串之间,如果分隔符为 NULL,则结果为 NULL。函数会忽略任何分隔符参数后的 NULL 值,但是CONCAT_WS()不会忽略任何空字符串;
- user()和current_user()的区别:
- 获取当前的日期:CURDATE(),获取当前时间:CURTIME(),获取当前日期时间:NOW()。
- where字句中!=、is null、is not null在MySQL5.7.21版本中不走索引;
- utf8编码用3个字节存储字符。
- 常见字符串函数:
- instr(string, substring): 返回substring第一次出现在string的起始位置,如果不存在substring返回0。
- strcmp(string1,string2): 两个字符串相同时,返回0;string1在字典中比string2靠后,返回1;否则返回-1。
- left(string, numstr): 返回字符串左边的numstr个字符;
- right(string,numstr): 返回字符串右边的numstr个字符;
- lpad(string, len, padstring): 如果string的字符数量大于len,那就从string截取len个字符返回,如果string的字符数量小于len,那就在string的前边添加padstring直到string有len个字符返回;
- rpad(string, len, padstring): 与lpad作用相反,在右边补充字符。
- substring(string, pos, length): 返回string中pos位置开始的len个字符,MySQL从1开始数。
- substring_index:
- trim
- 常见的DATE and TIME运算:
- 类型:DATE、TIME、DATETIME;
- 函数:NOW()、CURDATE()、CURTIME();
DATE_ADD()、DATE_SUB()。
- 常见的Numeric函数:
- ROUND(): 四舍五入;
- TRUNCATE():
- ceiling(x):对x进行向上取整。
- 系统参数:
- 所有创建的用户都在mysql库中的user表中;
-
select user();
: 获取当前真正连接数据库的用户; select current_user();
- 创建用户时,需要指定用户名、密码和客户端IP地址
create user user_name@ip_address identified by pwd;
- 聚合函数:
- GROUP BY:
- HAVING:
- LIMIT:
- GROUP_CONCAT()
- Union 与 union all
- temporary buffer: group by,union,distinct
- 唯一索引和普通索引的区别:只有普通索引能使用change buffer,普通索引适合于频繁写入,但不常常读取。
- MySQL没有函数索引和索引跳跃扫描;
- 执行计划中通过type判断查询走不走索引。
- key_len的算法:索引列类型if:1)int不能为空时=4;能为空时=5;2)varchar(20),由于加上变长,key_len=22;3)datetime=8;4)date=3。
- sync_binlog、innodb_flush_log_at_trx_commit:两个值都设为1时,称作双1模式。
用户访问数据库,如果需要sort,在内存中会生成一块私人的sort buffer,从innodb buffer pool中取数据,放到sort buffer中排序。
索引
- 索引是什么?
索引的出现是为了提高数据查询的效率,就像书的目录一样。 - 有哪些索引,适用于对哪一列进行索引?
-
常见的索引模型
- 哈希表:1)适合于单个数据进行精确查询,以及插入速度快;2)不适合范围查询,以及不适合做更新。
- 有序数组:根据索引对数据进行排序,1)范围查询速度快;2)插入和更新满,因为数组是连续存储的,因此在插入和更新时需要移动元素。适用于历史表。
- 二叉搜索树:由于分支数量为2,导致树较高,增加了查询数据时磁盘IO的次数,在数据库中不常使用。
- N叉搜索树(MySQL使用,也叫B+树):树根的数据块总是放在内存中,每一个索引在InnoDB中对应一棵B+树。
-
常见的索引
- 主键索引
- 唯一索引:由于索引定义了唯一性,查找到第一个满足条件的记录后,就会停止继续检索。
- 普通索引:查找到满足条件的第一个记录后,需继续查找下一个记录,直到碰到第一个不满足条件的记录。
- 前缀索引:即当要建立索引的字段长度太长时,从左往右截取一定长度的字符串建立索引,但是需要满足截取下来的子串区分度较大,
栗子:如果对订单号建立前缀索引时,由于订单前面几位通常以日期开头,此时区分度较小,可以将订单号反转。
- 索引设计原则
- 选择度越大,越适合建索引,选择度是指:对于一个表,随机抽出N行,在这N行中,对于索引列而言如果存在n个不同的值,我们称n/N为该列的选择度。MySQL通过收集统计信息来计算选择度,统计信息存储在.frm文件中,手动收集统计信息语句:
analyze table 表名;
,查看统计信息语句:show index from 表名;
;
执行计划的阅读
- 使用EXPLAIN命令查看SQL语句的执行计划,但是语句不会被执行;
- 执行计划的关键字段:
type: 值是range表示索引范围扫描(在sql语句的where字句表示范围查找),值是ALL表示全表扫描,值是ref表示等值索引扫描(等号查找);
possible_keys:所有查询时可能用到的索引;
key: 查询实际使用的索引;
key_len: 所使用索引的长度;
ref:
rows: 要查找的行数,从统计信息而来。
filtered:
Extra: 值是Using index表示当前查询用到了索引覆盖优化。 - 复制表中的所有数据到新表中:
create table t_new as select * from t;
- where字句中如果是数学表达式、带函数的列、!、<>和not in(都表示不等于)、is null就走不了索引。
- null和任何值做运算结果必然是null。
MySQL中的锁
undo保证事务的一致性,commit和rollback保证事务的原子性,锁保证隔离性,redo日志保证持久性。
- 全局锁
整个数据库只能读,不能写。Flush tables with read lock(FTWRL)
,适用于数据库进行备份时使用全局锁。 - 表级锁
包括表锁和元数据锁。
- 表锁
lock table t read;
: 给表t加读锁,此时表t只允许读,不允许写;
unlock tables;
: 释放锁;
lock table t write;
: 给表t加写锁,此时表t不允许读和写。 - 元数据锁(生产常用)
所有的SQL语句对表进行操作时,会对表加MDL锁(Metadata lock,元数据锁),此时无法对表的结构进行修改(比如增加或者减少列),因为修改表结构需要。
- 行级锁
- MySQL加锁的默认隔离级别是可重复读(tx_isolation=repeated read),即某个会话开启事务后,读取的数据结果不受其他客户端对数据进行修改的影响。
排序
- 在对数据进行sort时,innodb buffer pool中的数据会存放到分配给用户个人的sortbuffer(默认大小是256K,生产过程中一般分配64M)中进行排序,加入待排序的数据量小于sortbuffer size,此时直接在sortbuffer进行快速排序,如果大于sortbuffer size,将待排序数据分块放入sortbuffer中,排序之后写到磁盘中产生临时文件,所有块都排好后,在使用堆排序,直到整个数据都变成有序(执行计划中的Extra项显示using filesort);
- 在对数据进行sort时,如果sort列是索引,且select返回的结果就是该列数据,那么sort时直接从二级索引的叶子结点中取数据(执行计划中的extra项显示using index),不需要放入sortbuffer中排序。
高可用
- 在线热备;
- 读写分离:主库负责写,从库用来读。
- 一主三从库的用法:从库1分担读的压力,从库2用于备份,从库3用于主库宕机后的切换。金融科技需要保证数据一致性,可以牺牲一定的可用性。
- binlog cache: 记录用户的所有修改操作,commit之后,这些操作落盘保存到binlog文件中,特点:1)记录完整的事务,从begin:到commit;2)以commit的时间顺序记录;3)只记录提交的事务。从库进行备份时,主库只需将binlog文件传给从库,从库保存为Relaylog文件,从库执行即可。
- binlog cache相关的参数:sync_binlog,该值设为1时,表示一次commit就落盘一次;值设为0时,每秒落一次盘;设为>2的任意值N,即没N次落一次盘。
- 如果要求写入即读,只能采用单实例模式(主库读主库写)。
- 半同步可以防止主从复制数据丢失。
- MHA:主从架构,主库宕机后,MHA可从剩下的从库中自动选取与原先主库最接近的从库作为新的主库。
LVS+Keepalived:
PXC:Galery(支持多点写入),分布式数据库无法即时读写。
MGR: - 分表分库
- 中间件:mycat(开源)、DELE,不跨分片进行排序和查询,用分布式数据库时,事务不能跨分片做,保证不了事务的一致性。
数据库开发规范
- 创建表时,建表语句里一定要有自增主键
id int NOT NULL PRIMARY KEY AUTO_INCREMENT
,根据自增主键列建立索引时,每次插入数据都在索引最后面插入,不改变已有索引的结构。 - 主键长度越小,普通索引的叶子结点就越小,占用的空间也就越小,自增主键选用int类型时,占4个字节。
- 日期选用DATE类型,时间选用TIME类型,日期时间选用DATETIME类型(用1个字节存储),表示成YYYY-MM-DD HH:MM:SS。
索引树越高,磁盘的IO次数越多,索引效果越差。
MyISAM
包括主键索引(又叫聚簇索引)和辅助键索引,两种索引的叶子结点除了存放索引字段外,还包括指向记录的指针。
不支持事务。
MySQL是单进程多线程的服务器。
用户连接数据库,一个连接叫做一个session,空闲的session叫做idle,要对数据库操作的叫做active。