书籍推荐:mysql必知必会
深入浅出mysql
mysql高性能(偏DBA)
数据量较小:索引(一个库)
数据量较大:分库分表,读写分离
mysql优化是一个大的范围,需要丰富的经验,课程中会把作为一个java程序员(非DBA)基本的、常用的一一列举出来,希望大家尽可能多的掌握,因为太多了,而我们重点在java,所以每个知识点都只会抽取重点来学习。如果以后大家项目中需要这方面的,大家以这个为基础,再去深入学习,也是没有问题的。
select * from xxx where id=1
①建立连接1.1(Connectors&Connection Pool),通过客户端/服务器通信协议与MySQL建立连
接。MySQL 客户端与服务端的通信方式是 “ 半双工 ”。对于每一个 MySQL 的连接,时刻都有一个
线程状态来标识这个连接正在做什么。
通讯机制:
全双工:能同时发送和接收数据,例如平时打电话。
半双工:指的某一时刻,要么发送数据,要么接收数据,不能同时。例如早期对讲机
单工:只能发送数据或只能接收数据。例如单行道
线程状态:系统故障
show processlist; //查看用户正在运行的线程信息,root用户能查看所有线程,其他用户只能看自
己的
id:线程ID,可以使用kill xx;
user:启动这个线程的用户
Host:发送请求的客户端的IP和端口号
db:当前命令在哪个库执行
Command:该线程正在执行的操作命令
Create DB:正在创建库操作
Drop DB:正在删除库操作
Execute:正在执行一个PreparedStatement
Close Stmt:正在关闭一个PreparedStatement
Query:正在执行一个语句
Sleep:正在等待客户端发送语句
Quit:正在退出
Shutdown:正在关闭服务器
Time:表示该线程处于当前状态的时间,单位是秒
State:线程状态
Updating:正在搜索匹配记录,进行修改
Sleeping:正在等待客户端发送新请求
Starting:正在执行请求处理
Checking table:正在检查数据表
Closing table : 正在将表中数据刷新到磁盘中
Locked:被其他查询锁住了记录
Sending Data:正在处理Select查询,同时将结果发送给客户端
Info:一般记录线程执行的语句,默认显示前100个字符。想查看完整的使用show full
processlist;
②查询缓存(Cache&Buffer),这是MySQL的一个可优化查询的地方,如果开启了查询缓存且在
查询缓存过程中查询到完全相同的SQL语句,则将查询结果直接返回给客户端;如果没有开启查询
缓存或者没有查询到完全相同的 SQL 语句则会由解析器进行语法语义解析,并生成“解析树”。
缓存Select查询的结果和SQL语句
执行Select查询时,先查询缓存,判断是否存在可用的记录集,要求是否完全相同(包括参
数值),这样才会匹配缓存数据命中。
即使开启查询缓存,以下SQL也不能缓存
查询语句使用SQL_NO_CACHE
查询的结果大于query_cache_limit设置
查询中有一些不确定的参数,比如now()
show variables like '%query_cache%'; //查看查询缓存是否启用,空间大小,限制等
show status like 'Qcache%'; //查看更详细的缓存参数,可用缓存空间,缓存块,缓存多少等
③解析器(Parser)将客户端发送的SQL进行语法解析,生成"解析树"。预处理器根据一些MySQL
规则进一步检查“解析树”是否合法,例如这里将检查数据表和数据列是否存在,还会解析名字和别
名,看看它们是否有歧义,最后生成新的“解析树”。
④查询优化器(Optimizer)根据“解析树”生成最优的执行计划。MySQL使用很多优化策略生成最
优的执行计划,可以分为两类:静态优化(编译时优化)、动态优化(运行时优化)。
等价变换策略
A:5=5 and a>5 改成 a > 5 去除恒成立条件
a < b and a=5 改成b>5 and a=5
基于联合索引,调整条件位置等
优化count、min、max等函数
InnoDB引擎min函数只需要找索引最左边
InnoDB引擎max函数只需要找索引最右边
MyISAM引擎count(*),不需要计算,直接返回
提前终止查询
使用了limit查询,获取limit所需的数据,就不在继续遍历后面数据
in的优化
MySQL对in查询,会先进行排序,再采用二分法查找数据。比如where id in (2,1,3),变
成 in (1,2,3)
B:join优化:inner join/join 自动优化小表驱动大表,left right没法优化
select from a join b 10万个 20万--40万
select from b join a 1000个 2000-4000
a表大,b表小,基于主键索引,每条数据查询的IO次数2-4
⑤查询执行引擎负责执行 SQL 语句,此时查询执行引擎会根据 SQL 语句中表的存储引擎类型,以
及对应的API接口与底层存储引擎缓存或者物理文件的交互,得到查询结果并返回给客户端。若开
启用查询缓存,这时会将SQL 语句和结果完整地保存到查询缓存(Cache&Buffer)中,以后若有
相同的 SQL 语句执行则直接返回结果。
如果开启了查询缓存,先将查询结果做缓存操作
返回结果过多,采用增量模式返回
存储引擎:
MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。
这些不同的技术以及配套的相关功能在 MySQL中被称作存储引擎(也称作表类型)
因为在关系数据库中数据的存储是以表的形式存储的,所以存储引擎也可以称为表类型(Table Type,即存储和操作此表的类型)。
Mysql存储引擎分类:
MyISAM、InnoDB、MEMORY、MERGE等
CREATE TABLE `brand` (
`id` int(11) NOT NULL AUTO_INCREMENT
) ENGINE=InnoDB DEFAULT CHARSET=utf8; #指定引擎
默认存储引擎
SHOW ENGINES
InnoDB是事务型数据库的首选引擎,通过上图也看到了,InnoDB是目前MYSQL的默认事务型引擎,是目前最重要、使用最广泛的存储引擎。支持事务安全表(ACID),支持行锁定和外键。InnoDB主要特性有:
1、InnoDB mysql现在默认支持的引擎,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB会为每一行生成一个6字节的隐藏ROWID,并以此作为主键
2、支持事务安全表(ACID),支持行锁定和外键
场景:由于其支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果需要对事务的完整性要求比较高(比如银行),要求实现并发控制(比如售票),那选择InnoDB有很大的优势。因为支持事务的提交(commit)和回滚(rollback)。
总结:(理解并记住)
1.支持事务、支持外键
2.支持行级锁与表级锁
3.花费资源去处理事务,效率比不上MyISAM
4.有事务日志,恢复数据较方便
5.索引为聚簇索引
虽然InnoDB不断优化,效率已经有了很大提升,但是还是比不上MyISAM
MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事物和外键。
总结(记住)
Mysql 5.5版本之前的默认的存储引擎
1.不支持事务、不支持外键
2.只支持表级锁(后面介绍)
3.没有事务日志,故障恢复数据较麻烦
4.分区存放文件,平均分配IO,不用花费资源去处理事务,效率较高
5.索引为非聚簇索引
id 1 name 张三
update xxx set name=‘李四’ where id=1
mysql为提高效率,每次修改不会直接修改磁盘,会先修改buffer,然后再由mysql自己的线程purge去刷新到磁盘。为了支持回滚,得记录修改之前得状态
事务开启前,先写undo日志
保证了事务原子性
数据修改前的内容(用于回滚),当把数据从磁盘读取内存的时候,每次数据操作这些数据都会变脏,就是脏数据,需要都会记录undo日志。当发生回滚的时候,就需要这些日志用来回滚数据
Undo:意为撤销或取消,以撤销操作为目的,返回指定某个状态的操作。
Undo Log:数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数
据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响。
Undo Log产生和销毁:Undo Log在事务开始前产生;事务在提交时,并不会立刻删除undo
log,innodb会将该事务对应的undo log放入到删除列表中,后面会通过后台线程purge thread进
行回收处理。Undo Log属于逻辑日志,记录一个变化过程。例如执行一个delete,undolog会记
录一个insert;执行一个update,undolog会记录一个相反的update。
Undo Log存储:undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback
segment回滚段,内部包含1024个undo log segment。可以通过下面一组参数来控制Undo log存
储。
实现事务的原子性
Undo Log 是为了实现事务的原子性而出现的产物。事务处理过程中,如果出现了错误或者用户执
行了 ROLLBACK 语句,MySQL 可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态。
实现多版本并发控制(MVCC)
Undo Log 在 MySQL InnoDB 存储引擎中用来实现多版本并发控制。事务未提交之前,Undo Log
保存了未提交之前的版本数据,Undo Log 中的数据可作为数据旧版本快照供其他并发事务进行快
照读
事务A手动开启事务,执行更新操作,首先会把更新命中的数据备份到 Undo Buffer 中。
事务B手动开启事务,执行查询操作,会读取 Undo 日志数据返回,进行快照读
undo log是用来回滚数据的用于保障 未提交事务的原子性
数据修改后的内容,在缓冲池,然后数据修改后生成redo日志,需要把这些内存中的数据插入到磁盘。这个时候当数据库宕机的时候。这些redo就是重要的记录,重启之后会把redo日志也就是修改的数据重新写入数据库。
写buffer得时候,也会同时写redo日志,因为写redo(顺序io)日志的速度远远高于,写mysql数据(mysql数据以页为单位存储,随机io)
保证了ACID的持久性
(1)redo log 的存储是顺序存储,而缓存同步是随机操作。
(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。Redo:顾名思义就是重做。以恢复操作为目的,在数据库发生意外时重现操作。
Redo Log:指事务中修改的任何数据,将最新的数据备份存储的位置(Redo Log),被称为重做
日志。
Redo Log 的生成和释放:随着事务操作的执行,就会生成Redo Log,在事务提交时会将产生
Redo Log写入Log Buffer,并不是随着事务的提交就立刻写入磁盘文件。等事务操作的脏页写入
到磁盘之后,Redo Log 的使命也就完成了,Redo Log占用的空间就可以重用(被覆盖写入)。
Redo Log工作原理
Redo Log 是为了实现事务的持久性而出现的产物。防止在发生故障的时间点,尚有脏页未写入表
的 IBD 文件中,在重启 MySQL 服务的时候,根据 Redo Log 进行重做,从而达到事务的未入磁盘
数据进行持久化这一特性
redo log是用来恢复数据的 用于保障,已提交事务的持久化特性
(1)redo log 的存储是顺序存储,而缓存同步是随机操作。
(2)缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。
假设有A、B两个数据,值分别为1,2.
1. 事务开始
2. 记录A=1到undo log
3. 修改A=3
4. 记录A=3到 redo log
5. 记录B=2到 undo log
6. 修改B=4
7. 记录B=4到redo log
8. 将redo log写入磁盘
9. 事务提交
1.增删改,mysql会默认加写锁
2.select * from sing where s_num=‘10001’ lock in share mode;加读锁 读读可以共享
3.读写锁不共享
4.for update 加的写锁
共享锁与排他锁
共享锁(读锁):其他事务可以读,但不能写。
排他锁(写锁) :其他事务不能读取,也不能写。
MySQL 不同的存储引擎支持不同的锁机制,所有的存储引擎都以自己的方式显现了锁机制,服务器层完全不了解存储引擎中的锁实现:
MyISAM 和 MEMORY 存储引擎采用的是表级锁(table-level locking)
BDB 存储引擎采用的是页面锁(page-level locking),但也支持表级锁
InnoDB 存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
默认情况下,表锁和行锁都是自动获得的, 不需要额外的命令。
但是在有的情况下, 用户需要明确地进行锁表或者进行事务的控制, 以便确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成。
锁住整张表,开销小,加锁快;不会出现死锁(因为MyISAM会一次性获得SQL所需的全部锁);锁定粒度大,发生锁冲突的概率最高,并发度最低。 演示:两个cmd
lock table xxxx write/read
unlock tables
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高
select xxx for update
全局锁就是对整个数据库实例加锁。MySQL提供了一个加全局读锁的方法,命令是Flush tables with read lock
。当需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞:数据更新语句(数据的增删改)、数据定义语句(包括建表、修改表结构等)和更新类事务的提交语句
全局锁的典型使用场景是,做全库逻辑备份。也就是把整库每个表都select出来存成文本
但是让整个库都只读,可能出现以下问题:
1.详细的分析产品需求 会有哪些查询,哪些修改,好好建表,减少长事务操作
2.分布式事务中,采用弱一致性,可以减少阻塞
3.最直接最简单的方法就是把表的数据量变小(针对于数据量确实很大了,分库分表)
4.当然sql语句调优、提高代码质量等都是很重要的
如果真的出现了:
show full PROCESSLIST:查看状态,可以参考前面的属性,必要情况下 直接kill
相互等待对方释放锁,形成死锁
1.编号解死锁:对锁编号,按顺序锁。 比如AB 每个线程都做判断,始终先锁A 在锁B
2.首先在innodb搜索引擎中,会根据算法主动进行部分死锁的检测与释放,比如上面的例子自动释放。
3.大事务拆小。大事务更倾向于死锁,大事务锁的时间长,如果业务允许,将大事务拆小(DBA建表功底)。
4.为表添加合理的索引
最主要就是解决读写互斥,写时复制读写分离,空间换时间
id :1 name 张三
事务1:select * from xxx where id=1 ------>复制在一个地方
事务2:update xxx where id=1 数据库改
改的地方跟我读的不是同一个,读写分离
MVCC
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。什么是当前读和快照读?
在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?
当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
A事务:select * from xxxx in share mode 当前读,读取到数据库最新数据
一系列操作…
select * from xxxx in share mode 当前读,读取到数据库最新数据
因为加锁,如果没有锁,使用当前读,就会出现不可重复读,幻读
快照读
像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
A事务:select * from xxxx 把当前这个时刻的记录照个相,在内存中就是复制了一份数据
一系列操作。。。。操作的过程中有可能其它事务已经更改了数据
select * from xxxx 这次读取不会读到其它事务修改数据,因为在一个事务中如果没有触发当前读,那 么就只会有一次快照读
所以mvcc的快照读就在不加锁的锁 的情况下解决了不可重复读,既然没有加锁,那么这边查询,其它事务就可以更改,于是解决读写冲突
数据库并发场景有三种,分别为:(针对没有措施的情况下)
读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
第一类更新丢失:
第二类更新丢失
A事务覆盖B事务已经提交的数据,造成B事务所做操作丢失
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
id: 1 name zhangsan 事务id:1
DB_TRX_ID
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
DB_ROLL_PTR
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)undo日志记录了很多操作
当前的这条数据,得找到对应得日志,靠指针
DB_ROW_ID
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
undo日志只是记录得事务操作得反向操作.
A事务正在执行,把buffer age改成25,但是没有提交
B事务来读取数据,应该读取24,24哪儿来呢
什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图
当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大
Read View简单的理解成有三个全局属性:
trx_list
一个数值列表,用来维护Read View生成时刻系统正活跃的事务ID
例如:
我自己的事务时事务2
1操作未提交 3操作未提交 4已经提交 放入trx_list:[1,3]
low_limit_id
记录trx_list列表中事务ID最小的ID 就是1
next_limit_id
ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1
5
1.如果当前buffer里面的事务id x小于trx_list里面的最小id(low_limit_id) 可以读取这个事务id x的数据
事务2开启,事务3开启,我是事务4,事务5 , 事务id是自增的 buffer的顺序应该1-2-3-4-5,那个事务最近操作了我,事务id就是谁
事务4开启事务,生成快照读,读取到buffer中当前字段的隐藏事务id是1. 因为事务1的操作一定在2345前面,所以事务id能为1一定是事务1回滚或者提交了
第一条满足就不会走后面
2.如果第一条没有满足,比较读取到buffer中的事务id,比next_limit_id还大,不可读取,我生成快照的时候最大值就是5,你比5还大说明生成快照的时候压根没有开启这个事务,肯定不能读取。
3.2条满足的情况下,把在buffer中读取到的事务id,去trx_list里面遍历,如果trx_list里面有说明还在活跃,不能读取,否则可以读取
因为这三条,使用mvcc 乐观锁来保证并发隔离性,并没有加锁
会出现不可重复读
A事务: 第一次select 生成快照
第二次select 又会生成快照
每一次都会生成快照
会不会脏读?不会,因为上面的mvcc条件判断
会不会不可重复读呢?会出现, 比如上面的例子,第一次4提交,3没有提交。buffer中的事务id是4 根据条件4满足直接读取。如果第二次快照3提交了,buffer中事务id是3,根据地址。直接定位3的版本链.两次的结果不一致
readview会改变
只是多次读取,只会有一次快照
A事务: 第一次select 生成快照
第二次select 不会生成
肯定避免了不可重复读
A事务第一次读取了,另外一个事务修改了,A事务再次读取数据一致
如果只是多次读取是可以解决幻读的,因为不会出现当前读,只会生成一次快照。
1.a事务先select(触发快照生成),b事务insert确实会加一个gap锁,但是如果b事务commit,这个gap锁就会释放(释放后a事务可以随意操作), student表9条数据,生成快照,b事务插入/删除了一条数据,并且提交
2.a事务再select出来的结果在MVCC下还和第一次select一样,不会幻读,生成了一次快照了不会再次生成,没有幻读
3.接着a事务不加条件地update,这个update会作用在所有行上(包括b事务新加的),
update 需要更新全表
增删改触发锁,触发当前读,需要读取最新的数据,读取到10条
4.a事务再次select就会出现b事务中的新行,并且这个新行已经被update修改了. 刚才的快照读已经丢失了,因为在中间有一次当前读
上面这样,事务2提交之后,事务1再次执行update,因为这个是当前读,他会读取最新的数据,包括别的事务已经提交的,所以就会导致此时前后读取的数据不一致,出现幻读。
InnoDB有三种行锁的算法:
尽量弥补错误。
1,Record Lock:单个行记录上的锁。
2,Gap Lock:间隙锁,锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
3,Next-Key Lock:1+2,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题
超时时间的参数:innodb_lock_wait_timeout ,默认是50秒。
超时是否回滚参数:innodb_rollback_on_timeout 默认是OFF。
默认情况下,InnoDB存储引擎不会回滚超时引发的异常,除死锁外。
因为InnoDB对于行的查询都是采用了Next-Key Lock的算法,锁定的不是单个值,而是一个范围,按照这个方法是会和第一次测试结果一样。但是,当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。
注意:通过主键或则唯一索引来锁定不存在的值,也会产生GAP锁定
如何让测试一不阻塞?可以显式的关闭Gap Lock:
1:把事务隔离级别改成:Read Committed,提交读、不可重复读。SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
2:修改参数:innodb_locks_unsafe_for_binlog 设置为1。
mvcc+间隙锁:完成了事务隔离性,但是依然没有彻底解决幻读吧
对ACID的回答要提高,幻读,不可重复读
undo日志:保证原子性
redo日志:保证持久性
mvcc+锁:保证隔离性
原子性+持久性+隔离性=一致性(最终目的)