mysql必知必会
Posted 聪明鱼聪明故事
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mysql必知必会相关的知识,希望对你有一定的参考价值。
mysql基础架构
存储引擎默认是innoDB
1.连接器:负责客户端和server建立链接,维持,管理连接等
•应尽量减少连接动作,使用长链接•长链接造成的OOM和内存占用过大可以通过定期断开重连和执行musql_reset_connection来重置链接资源
2.查询缓存
•因为数据表会经常更新,所以查询缓存失效频繁,不建议使用•除非你的业务是一张静态表•mysql8.0中查询缓存功能被删除了
3.分析器:一般就是用来检查语法的
4.优化器:一般用来决定选择用哪个索引,决定多表关联下各个表的连接顺序,优化器不影响查询结果,只影响查询效率
5.执行器
•1.判断当前用户对要查询的表是否有查询权限•2.有权限就根据表的引擎,使用这个引擎所提供的接口•3.对值的查找和范围判断之类的都在这部分完成,返回结果也通过执行器
##mysql三大范式
1.原子性:数据的每一行都不允许再被分割
2.唯一性:首先该范式的满足条件必须要先满足范式1,该范式是指,主键必须能够唯一的区分数据,即非主键依赖主键
3.满足该范式必须先满足范式2,该范式指每个非主键属性不能依赖与其他非主键属性,比如部门和经理两个属性,如果部门经理唯一的话,那么由部门就可以推出其经理,相当与存经理这个key本身是浪费了资源
事务的acid
事务的acid是衡量一个事务的四个维度,而并不是事务必须满足acid的要求
1.a-->原子性:一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做,通过undo log来保障
2.c-->持久性:事务一旦提交,它对数据库的改变就应该是永久性的,通过redo log来保障
3.i-->隔离性:事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰,通过锁和事物的隔离级别来保障
4.d-->一致性:事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态,原子性、持久性和隔离性,都是为了保证数据库状态的一致性
redo log
1.redo log就是WAL(Write Ahead Logging)技术的实现方法,用于解决crash-safe(字面意思),是innoDB特有的日志
2.sql语句执行的时候会把执行记录写入redo log,本质就是写入内存,等适当的时候调用fsync接口对redo log进行刷盘,当出现crash-safe的时候可以根据redo log记录的操作进行数据库回滚恢复
3.redo log的结构:
•1.大小固定,一组4个文件,每个文件1gb•2.使用write pos指针记录当前位置,write pos边写边向后移动,checkpoint指针记录擦除位置,通俗的说chechpoint-write pos的空间就是当前空着的空间,可以写入•3.redo log采用循环记录的方式,例如4个文件分别是abcd,d文件写完之后会回到a文件开头继续写,当write pos追上checkpoint位置的时候就是写满了,要对文件进行擦除,同时被擦除的部分要写入磁盘
4.物理日志,记录了“某个数据页上修改了什么”
5.本质上redo log做的事情是:将已经修改的操作进行记录,使得数据库在出现异常时不会丢失最新的操作
binlog
1.binlog是所有执行引擎都可以使用的日志
2.并不是循环记录的,而是不断追加写入的,不会覆盖以前的日志
3.逻辑日志,记录了“给id=2这一行的c字段+1”这种东西
4.只能用于归档,没有crash-safe能力
binlog和redo log两种日志一般一起使用,进行两阶段提交,两阶段提交的意思就是:先写入redo log,进入prepare阶段,然后写入binlog,然后让redo log进行commit,如果不进行两阶段提交而是先写一个再写一个的话会导致其中一个日志回复的数据和原库不同
innode_flush_log_at_trx_commit参数控制redo log的commit频率,建议设置为1,这样每次事务的redo log都会写入磁盘
sync_binlog同上
sql语句的执行过程
1.执行器对语句给出的条件进行查找
2.innoDB查找当前数据页是否在内存中,如果在则返回页中的该行数据,不在就去磁盘里找,读入内存
3.执行器执行事务
4.innoDB将执行结果更新到内存
5.将操作记录写入redo log,让其处于准备阶段
6.将操作记录写入binlog
7.提交事务,并commit redo log
mysql的数据类型
1.整型
•1.tinyint smalliont,meduimint,int•2.bigint(用于记录大数)
2.浮点型
•1.float(8字节) double(16字节)•2.decimal(大数,用于存储货币单位)
3.日期
•1.date,time,year•2.timestamp(4字节,1970-2038年) datetime(8字节,一般比timestamp好用)
4.varchar
•用于存储可变长字符串,比定长更节省空间•使用varchar(n)指定长度•使用1到2个额外的字节记录字符串长度•由于程度可变,需要update时做额外工作,会影响update效率•适用场景:•字符串列长度大于平均长度•列的更新很少•使用了像utf-8这样复杂的字符集
5.char
•定长•适合存储短字符串
预读机制
磁盘读写,并不是按需读取,而是按页读取,一次至少读一页数据(一般是4K),如果未来要读取的数据就在页中,就能够省去后续的磁盘IO,提高效率
buffer pool
1.为了解决磁盘上磁盘速度和CPU速度不一致的问题,在操作磁盘上的数据时,先将数据加载至内存中,在内存中对数据页进行操作,缓冲池一般也是按页缓存数据
2.innodb_buffer_pool_size:配置缓冲池大小
3.innode_old_blocks_pct:老生代占lru链长度的比例,默认3:7
4.innode_old_blocks_time:老生代停留时间窗口,单位是毫秒,即同时满足被访问和老生代停留时间超过1秒两个条件才会被插入到新生代头部
5.buffer pool的实现原理(特殊的lru):
•将LRU分为两个部分:新生代(new sublist)和老生代(old sublist)•新老生代收尾相连,即:新生代的尾(tail)连接着老生代的头(head);•新页(例如被预读的页)加入缓冲池时,只加入到老生代头部:•如果数据真正被读取(预读成功),才会加入到新生代的头部•如果数据没有被读取,则会比新生代里的“热数据页”更早被淘汰出缓冲池
6.为什么不使用传统lru:
•1.预读失败:页被提前放进了缓冲池,结果没读•2.缓冲池污染:某个sql语句如果扫描大量数据可能会把缓冲池里的页全顶出去,导致大量热数据被换出,影响mysql性能
7.优化缓冲池污染,思路是:
•不让批量扫描的大量数据进入到 new 区•让真正被读取的页,才挪到缓冲池 LRU 的头部•具体实现:加入了一个“old 区停留时间”的机制:在 old 区域的缓存页进行第一次访问时就在它对应的控制块中记录下来这个访问时间,如果后续再次访问的时间与第一次访问的时间在某个时间间隔内(即该缓存页在 old 区的存在时间在某个时间间隔内),那么该页面就不会被从old 区移动到 new 区的头部
事务隔离
1.隔离级别:
•读未提交:事务提交前其他事务就可以看到•读提交:事务提交后其他事务才能看到•可重复读:一个事务执行开启阶段会创建一个当前数据状态的静态视图,执行过程中所有的数据都以这个静态视图为准•串行化:对读写进行加锁,防止多线程访问临界资源
2.使用场景:
•可重复读隔离级别常用于银行账户月底账单明细校对,这样在校对数据事务执行过程中余额发生了变化也以校对数据事务执行初始创建的那张静态视图为准
3.实现原理:
•使用回滚日志(undo log)和read-view实现
4.undo log:
•每个回归日志对应一个read-view,保存的就是执行的操作,比如将2改成1之类的,而回滚的时候只要逆向操作(将1改成2)就可以了•回滚日志在当前系统里没有比这个日志更早的read-view时就删除了•undo log本质上做的事情是:当数据库出现异常或者数据错误,操作原子性被破坏(某个事务执行了一半就挂了)之类的情况的时候,可以将最新的错误操作通过回滚消除,使得数据库回复到错误操作执行前的状态,这也是undo log和redo log的区别
5.MVCC:mvcc本质上就是由undo log实现的可重复读
•mvcc是什么:多版本并发控制,是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,维持一个数据的多个版本,使得读写操作没有冲突•当前读:抽象概念,就是读取的时候加锁,读取当前最新的数据并且保证其他线程不能修改,悲观锁•快照读:抽象概念,就是除了串行化以外的其他隔离级别下读取,读到的不一定是最新数据,不用加锁,mvcc要做的就是快照读而不是当前读•mvcc的具体实现:除了上面说到的undo log,还有另外两种实现方法•3个隐式字段:每行数据多记录三个字段•DB_TRX_ID:6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID•DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)•DB_ROW_ID:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引(主键索引)•Read View的实现:•innoDB中每个事务有一个唯一id,transaction id,每行数据也有多个版本,每个版本有个事务id叫row trx_id,也就是说一行记录其实有多个版本•实际上视图中记录的内容你可以理解为:某一行数据的n个版本,每个版本被transaction id为xx的事务更新,因此这个版本的row trx_id也为xx•而实际上视图中的多个版本并不真正的存在,而是依靠undo log记录的操作回滚倒推出来的•因此一个事务只要在启动的时候保证只认自己启动时刻和之前的生成就可以保证可重复读了•具体的做法是:使用一个视图数组记录事务,数组分为三块,如下图•其中已提交事务的部分可见,未开始事务的部分不可见,未提交事务的部分根据当前事务的row trx_id判断,如果id在这个部分的数组里,则说明没提交,不可见,否则说明提交了,可见
并发一致性问题
1.丢失修改:T1和T2两个事务都对一个数据进行修改,T1先修改,T2后修改,T2的修改覆盖了T1的修改,解决方法:未读提交,使得其他事务修改的时候能看到
2.读脏数据:T1修改一个数据,T2随后读取这个数据。如果T1撤销了这次修改,那么T2读取的数据时脏数据,解决方法:已读提交,只有提交后的数据才能够真正的产生修改
3.不可重复读:T2读取一个数据,T1对该数据做了修改。如果T2再次读取这个数据,此时读取结果和第一次读取的结果不同,解决方法:可重复读,这样t2在执行开启时和结束时读到的数据都是静态试图在事务开启时的快照,并且行锁其实也能解决这个问题
4.幻影读:T1读取某个范围内的数据,T2在这个范围内插入新的数据,T1再次读取这个范围内的数据,此时读取的结果和第一次读取的结果不同,解决方法:串行化,本质上其实就是表锁,这样一个表被锁住之后在这个表的范围内无法再插入新的数据了,自然不会产生这个问题
索引
1.innoDB中的索引:
•采用b+tree结构,叶子节点记录的是数据,非叶子节点用于查找•索引分为普通索引和主键索引,普通索引可以用不同的属性作为索引,一种索引对应一颗b+tree•主键索引:主键索引的叶子节点是多个page的数据•普通索引:不论选取什么作为索引,只要不是主键,那么叶子节点存的其实是主键索引,查询的时候先查询到待查询事务的主键,再进行主键索引查询,这个叫做回表•索引结构:innoDB的索引结构使用b+tree,每层的分叉大概在1200左右,这样树的层高一般是2到4层,查询一个值最多需要访问三次,叶子节点中的page之间头尾用链表相接,并且叶子节点根据特征进行排序,这样进行范围查询的时候需要跨叶子节点进行查找可以不需要再从根节点遍历一次•索引维护:•为了保证叶子节点的有序性,主键索引建议使用自增索引,因为这样可以在每次add操作的时候不需要移动叶子,直接追加即可•在普通索引下,叶子节点存放的是主键,所以主键长度越小,则占用空间越小•覆盖索引:前面说过的回表会扫描两次b+tree,如果普通索引本身就是要查询的事务,则mysql提供覆盖索引,不需要回表,如果某个属性需要经常查询且不为主键,建议设置为普通索引•最左前缀原则:索引内存放的字段会自动排序,比如如果以字符串类型作为索引,那么“abc”,“baa”,“acc”的排序顺序为 “abc” “acc” “baa”,可以进行快速查询,比如查询a开头的全部索引,查询到第一个之后只要向后顺序遍历即可,这个原则也适用于联合查询(第一个索引字段相同的话就按照第二个开始排序)•索引下推:本质上就是一种支持逻辑判断的索引覆盖,例如要查询开头为a且id为10的字段,同时索引为(str,id),那么在查询过程中会直接根据索引的id来获取需要的数据,不回再得到数据后判断一次
innoDB的数据存储结构
1.tablespace:存的是索引,本质上其实就是很多棵b+tree,每种索引对应一个tablespace,tablespace里的节点就是b+tree里的叶子节点和非叶子节点,这里叫做段
2.段:段里保存的是一个个区数据,在索引数据增长的过程中,申请的内存空间都是从段这个概念申请出来的,一个段的大小可以无线延伸
3.区:一个区=64个页,大小为1m,物理空间和逻辑空间都连续,一个区用完了就再去找段申请
4.page:一个page是16kb,是innodb管理的最小单位,逻辑空间和物理空间都连续,页面用完去找区申请,数据,日志和事务等等的数据都可以作为一种page的类型进行存储比如(undo log page,b-tree node等)
5.行:一个page存的就是多个行,这个行指的就是一行数据的意思,在mysql45讲中提到过叶子节点存的是行数据,其实这个意思是说存的是多行数据组成的多个page,而不是一行数据,innoDB中的读写最基本单位是page,而不是行,意味着读取一行数据其实读到的并不是一行而是多行
change buffer
1.change buffer的本质是cud(没有r没有r没有r)时如果要查询的page不在内存中,则将cud操作本身缓存在change buffer中,等待下次需要r的时候直接将数据从磁盘取出后执行change buffer中的操作之后再r,有效减少磁盘io,降低buffer pool的占用,实际上change buffer使用的临时内存就是从buffer pool里来的
2.change buffer虽然在内存有拷贝,但是也会被写入磁盘
3.change buffer的操作写入原始页被称为merge,除了r的时候会触发merge,系统后台没事儿定期也会merge一下,数据库正常关闭的时候也会merge一下
4.唯一索引的r不能使用change buffer,因为唯一索引在u的时候会通过读取数据检查操作是否违反唯一性约束,所以cud后不及时写入磁盘会导致无法判断,同时这也说明选择普通索引的u效率会高于唯一索引
5.由于每次写入磁盘是在r的时候,所以r的越少写入磁盘的次数就越少,那么change buffer就很适合写多读少的场景了,尤其是机械硬盘,毕竟io慢,少io的话能提升不少速度
6.和wal的区别:wal(也就是redo log)主要节省了写io的消耗(写的次数少了),change buffer主要节省的是读io的消耗(读的次数少了)
7.change buffer如果遇到机器突然断电是不会丢失的,哪怕没有写入磁盘,因为change buffer本身的操作其实使用wal记录了(日志),只需要重启后回归并merge就可以了
锁
1.全局锁(FTWRL)
•对整个数据库实例加锁,整个数据库只读,其他语句阻塞,典型使用场景是做全库逻辑备份•缺点:主库加全局锁所有业务要停止,从库加全局锁备份期间不能执行主库的binlog,导致主从延迟•优点:如果使用的引擎不支持可重复读这种隔离级别(MyISAM这种),那么想要实现全库一致性,只能加全局锁
2.全库只读:
•set global readlony=true•不建议使用,如果需要全库只读建议使用FTWRL,因为出现异常时FTWRL会释放锁,而全库只读会一直保持readolny的状态
3.表级锁:
•1.表锁:•lock tables ... read/write•可在断开客户端连接时主动释放•会限定本线程操作,比如线程a锁了表1和表2的写和读,那么其他线程不能写1不能读2,本线程也只能读1和写2•2.MDL:•不需要显示使用,mysql会自动加•在对表进行CRUD的时候加上读锁(防止别的线程读到奇怪的数据)•对表结构进行变更的时候加写锁(防止误写)•读锁不互斥,写锁互斥,读写之间也互斥•缺点:会遇到异常阻塞,比如事务a进行查询,加读锁,事务b进行查询,加读锁,此时没问题,但是事务c对表添加一个新项(改变结构),此时会阻塞,因为a的锁还没放,然后再有其他事物进行查询,会因为事务c的阻塞而阻塞,现在整个表不能读了,如果请求量大并且事务a查询速度慢,那么很快线程会爆满并崩溃•3.如何安全加锁:•如果有长事务,则尽量放到后面做•如果长事务必须现在做,那么后面的所有改变表结构事务最好设置一个等待时间,超过这个时间则放弃事务,不要造成后面的CRUD事物阻塞
4.行锁
•行锁就针对一行数据加锁•行锁需要主动加,并且不会在这一行数据的事务执行完毕后释放,而是整个事务结束了才会释放,这个也叫两阶段锁协议•死锁检测:死锁就是事物a锁了数据1,然后事务b锁了数据2,事务b更新完数据2后要更新数据1,阻塞等待事物a释放,事物a则更新完数据1后要更新数据2,而数据2被事务b锁了,这样ab两个事物就会互相无限等待对方释放,造成死锁•1.设置超时等待时间•2.发起死锁检测,发现死锁后将死锁回路中某个事务按照日志回滚,直到其他事务可以继续执行,将innobe_deadlock_detect设置为on开启该策略•由于策略1的时间设置长了会导致等待时间过长,设置断了会导致正常等待被误判为死锁,所以常用策略2•死锁检测的缺陷:•由于死锁检测每次都要对回路进行检查,消耗巨大,比如1000个并发的死锁检测可能是100万这个量级,会造成cpu大量消耗,尤其是如果没有死锁,那就太浪费了•解决方案:•1.如果确定不会出现死锁的话就临时关了死锁检测•2.进行并发控制:限制同一时间的并发数量
5.关于锁的总结
•表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。•意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。•意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。•行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。•共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。•排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
池化思想
1.池化本质就是将各种资源预先加载,这样获取的时候效率就高了
2.线程池使用阻塞队列来对数据进行缓冲
3.数据库本质是一个socket连接,数据库的池化本质上是连接的池化,每次数据库重新连接后不需要再次建立,这就是数据库连接池
以上是关于mysql必知必会的主要内容,如果未能解决你的问题,请参考以下文章