如图:
大体来说,MySQL可以分为Server层和存储引擎层两部分。
Server层包括连接器、查询缓存、分析器、优化器、执行器等,提供了Mysql Server 数据库所有逻辑功能,涵盖MySQL的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图、函数等。
存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持InnoDB、MyISAM、Memory等多个存储引擎。
存储引擎是MySQL中具体与文件打交道的子系统,MySQL区别于其他数据库的最重要特点是其插件式的表存储引擎,其根据文件访问层抽象接口来定制一种文件访问的机制(该机制叫存储引擎)。物理文件包括:redolog、undolog、binlog、errorlog、querylog、slowlog、data、index等。也就是说,你执行create table建表的时候,如果不指定引擎类型,默认使用的就是InnoDB。不过,你也可以通过指定存储引擎的类型来选择别的引擎。
连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:
mysql -h$ip -P$port -u$user -p
连接命令中的mysql是客户端工具,用来跟服务端建立连接。在完成经典的TCP握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。
如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在show processlist命令中看到它:
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数wait_timeout
控制的,默认值是8小时。
数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。建立连接的过程通常是比较复杂的,所以建议尽量使用长连接。
全部使用长连接后,你可能会发现,有些时候MySQL占用内存涨得特别快,这是因为MySQL在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是MySQL异常重启了。两种两种方案:
1、定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
2、如果你用的是MySQL 5.7或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
连接建立完成后,你就可以执行select语句了。执行逻辑就会来到第二步:查询缓存。MySQL拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以key-value对的形式,被直接缓存在内存中。key是查询的语句,value是查询的结果。如果你的查询能够直接在这个缓存中找到key,那么这个value就会被直接返回给客户端。如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。
但是,查询缓存往往弊大于利。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此,对于更新压力大的数据库来说,查询缓存的命中率会非常低。可以将参数query_cache_type设置成DEMAND,这样对于默认的SQL语句都不使用查询缓存。对于确定要使用查询缓存的语句,可以用SQL_CACHE显式指定:
mysql> select SQL_CACHE * from T where ID=10;
需要注意的是,MySQL 8.0版本直接将查询缓存的整块功能删掉了。
如果没有命中查询缓存,就要开始真正执行语句了。分析器先会做“词法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法。语法分析之后是语义解析,即检查表名、列名等是否存在,是否正确。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。看个例子:
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20;
既可以先从表t1里面取出c=10的记录的ID值,再根据ID值关联到表t2,再判断t2里面d的值是否等于20。
也可以先从表t2里面取出d=20的记录的ID值,再根据ID值关联到t1,再判断t1里面c的值是否等于10。
两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。
开始执行的时候,要先判断一下你对这个表T有没有执行查询的权限,如果没有,就会返回没有权限的错误。如果命中查询缓存,会在查询缓存放回结果的时候,做权限验证。查询也会在优化器之前调用precheck验证权限)。示例:
mysql> select * from T where ID=10
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。比如我们这个例子中的表T中,ID字段没有索引,那么执行器的执行流程是这样的:
1、调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中;
2、调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
3、执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
MySQL 必须要运行一个服务,监听默认的 3306 端口。
开发系统跟第三方对接的时候,必须要弄清楚的有两件事。
1、通信协议,比如我们是用 HTTP 还是 WebService 还是 TCP?
2、消息格式,比如我们用 XML 格式,还是 JSON 格式,还是定长格式?报文头长度多少,包含什么内容,每个字段的详细含义。
MySQL 是支持多种通信协议的,可以使用同步/异步的方式,支持长连接/短连接。使用异步方式的话,服务端带来巨大的压力(一个连接就会创建一个线程,线程间切换会占用大量 CPU 资源);另外异步通信还带来了编码的复杂度,所以一般不建议使用。如果要异步,必须使用连接池,排队从连接池获取连接而不是创建新连接。
MySQL 既支持短连接,也支持长连接。短连接就是操作完毕以后,马上 close 掉。长连接可以保持打开,减少服务端创建和释放连接的消耗,后面的程序访问的时候还可以使用这个连接。一般我们会在连接池中使用长连接。
连接时长默认为8小时。
查看 MySQL 当前有多少个连接命令:
show global status like 'Thread%';
Threads_cached:缓存中的线程连接数。
Threads_connected:当前打开的连接数。
Threads_created:为处理连接创建的线程数。
Threads_running:非睡眠状态的连接数,通常指并发连接数。
可以用"show processlist"查看连接状态,一些常见的状态:
MySQL 服务允许的最大连接数是多少呢?在 5.7 版本中默认是 151 个,最大可以设置成 16384(2^14)。查看命令是:
show variables like 'max_connections';
MySQL 支持哪些通信协议呢?第一种是 Unix Socket,默认是使用该协议,如果指定-h 参数,就会用第二种方式,TCP/IP 协议:
mysql -h192.168.8.211 -uroot -p123456
编程语言的连接模块都是用TCP协议连接到MySQL服务器的。
MySQL 使用了半双工的通信方式,所以客户端发送 SQL 语句给服务端的时候,(在一次连接里面)数据是不能分成小块发送的,不管你的 SQL 语句有多大,都是一次性发送。另一方面,对于服务端来说,也是一次性发送所有的数据,不能因为你已经取到了想要的数据就中断操作,这个时候会对网络和内存产生大量消耗。
所以,一定要在程序里面避免不带 limit 的这种操作,比如一次把所有满足条件的数据全部查出来,一定要先 count 一下。如果数据量的话,可以分批查询。
MySQL 的缓存默认是关闭的。为什么 MySQL 不推荐使用它自带的缓存呢?主要是因为 MySQL 自带的缓存的应用场景有限,第一个是它要求 SQL 语句必须一模一样,中间多一个空格,字母大小写不同都被认为是不同的的 SQL。第二个是表里面任何一条数据发生变化的时候,这张表所有缓存都会失效,所以对于有大量数据更新的应用,也不适合。
这一步主要做的事情是对语句基于 SQL 语法进行词法和语法分析和语义的解析。词法分析就是把一个完整的 SQL 语句打碎成一个个的单词。接下来就是语法分析,语法分析会对 SQL 做一些语法检查,比如单引号有没有闭合,然后根据 MySQL 定义的语法规则,根据 SQL 语句生成一个数据结构。这个数据结构我们把它叫做解析树(select_lex):
在解析的时候报错,解析 SQL 的环节里面有个预处理器。它会检查生成的解析树,解决解析器无法解析的语义。比如,它会检查表和列名是否存在,检查名字和别名,保证没有歧义。预处理之后得到一个新的解析树。
优化器最终会把解析树变成一个查询执行计划,查询执行计划是一个数据结构。MySQL 提供了一个执行计划的工具。我们在 SQL 语句前面加上 EXPLAIN,就可以看到执行计划的信息。
注意 Explain 的结果也不一定最终执行的方式。
数据库的表在存储数据的同时,还要组织数据的存储结构,这个存储结构就是由我们的存储引擎决定的,所以我们也可以把存储引擎叫做表类型。在 MySQL 里面,支持多种存储引擎,他们是可以替换的,所以叫做插件式的存储引擎。
MyISAM 和 InnoDB 是我们用得最多的两个存储引擎,在 MySQL 5.5 版本之前,默认的存储引擎是 MyISAM,5.5 版本之后默认的存储引擎改成了 InnoDB。
1.支持表级别的锁(插入和更新会锁表)。不支持事务。
2.拥有较高的插入(insert)和查询(select)速度。
3.存储了表的行数(count 速度更快)。
1.支持事务,支持外键,因此数据的完整性、一致性更高。
2.支持行级别的锁和表级别的锁。
3.支持读写并发,写不阻塞读(MVCC)。
4.特殊的索引存放方式,可以减少 IO,提升查询效率。
如何选择存储引擎?
如果对数据一致性要求比较高,需要事务支持,可以选择 InnoDB。
如果数据查询多更新少,对查询性能要求比较高,可以选择 MyISAM。
如果需要一个用于查询的临时表,可以选择 Memory。
更新流程和查询流程,基本流程也是一致的,也就是说,它也要经过解析器、优化器的处理,最后交给执行器。区别就在于拿到符合条件的数据之后的操作。
InnnoDB 的数据都是放在磁盘上的,InnoDB 操作数据有一个最小的逻辑单位,叫做页(索引页和数据页)。对于数据的操作,不是每次都直接操作磁盘,因为磁盘的速度太慢了。InnoDB 使用了一种缓冲池的技术,也就是把磁盘读到的页放到一块内存区域里面。这个内存区域就叫 Buffer Pool。
下一次读取相同的页,先判断是不是在缓冲池里。下一次读取相同的页,先判断是不是在缓冲池里面,如果是,就直接读取,不用再次访问磁盘。
修改数据的时候,先修改缓冲池里面的页。内存的数据页和磁盘数据不一致的时候,我们把它叫做脏页。InnoDB 里面有专门的后台线程把 Buffer Pool 的数据写入到磁盘,每隔一段时间就一次性地把多个修改写入磁盘,这个动作就叫做刷脏。
内存结构
Buffer Pool 主要分为 3 个部分:Buffer Pool、Change Buffer、Adaptive Hash Index,另外还有一个(redo)log buffer。
同样是写磁盘,为什么不直接写到 db file 里面去?为什么先写日志再写磁盘?
磁盘的最小组成单元是扇区,通常是 512 个字节。操作系统和内存打交道,最小的单位是页 Page。操作系统和磁盘打交道,读写磁盘,最小的单位是块 Block。
如果我们所需要的数据是随机分散在不同页的不同扇区中,那么找到相应的数据需要等到磁臂旋转到指定的页,然后盘片寻找到对应的扇区,才能找到我们所需要的一块数据,一次进行此过程直到找完所有数据,这个就是随机 IO,读取数据速度较慢。假设我们已经找到了第一块数据,并且其他所需的数据就在这一块数据后边,那么就不需要重新寻址,可以依次拿到我们所需的数据,这个就叫顺序 IO。
刷盘是随机 I/O,而记录日志是顺序 I/O,顺序 I/O 效率更高。因此先把修改写入日志,可以延迟刷盘时机,进而提升系统吞吐。当然 redo log 也不是每一次都直接写入磁盘,在 Buffer Pool 里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认 16M,它一样可以节省磁盘 IO。
redo log 的内容主要是用于崩溃恢复。磁盘的数据文件,数据来自 buffer pool。redo log 写入磁盘,不是写入数据文件。
Log Buffer 什么时候写入 log file?
在我们写入数据到磁盘的时候,操作系统本身是有缓存的。flush 就是把操作系统缓冲区写入到磁盘。log buffer 写入磁盘的时机,由一个参数(innodb_flush_log_at_trx_commit)控制,默认是 1。
redo log特点:
1、redo log 是 InnoDB 存储引擎实现的,并不是所有存储引擎都有。
2、不是记录数据页更新之后的状态,而是记录这个页做了什么改动,属于物理日志。
3、redo log 的大小是固定的,前面的内容会被覆盖。
check point 是当前要覆盖的位置。如果 write pos 跟 check point 重叠,说明 redo log 已经写满,这时候需要同步 redo log 到磁盘中。
MySQL 的内存结构,总结一下,分为:Buffer pool、change buffer、Adaptive Hash Index、 log buffer。
磁盘结构
磁盘结构里面主要是各种各样的表空间,叫做 Table space。表空间可以看做是 InnoDB 存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。InnoDB 的表空间分为 5 大类。
数据字典:由内部系统表组成,存储表和索引的元数据(定义信息)。
双写缓冲(InnoDB 的一大特性):InnoDB 的页和操作系统的页大小不一致,InnoDB 页大小一般为 16K,操作系统页大小为 4K,InnoDB 的页写入到磁盘时,一个页需要分 4 次写。
如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的情况,比如只写了 4K,就宕机了,这种情况叫做部分写失效(partial page write),可能会导致数据丢失。有个问题,如果这个页本身已经损坏了,用它来做崩溃恢复是没有意义的。所以在对于应用 redo log 之前,需要一个页的副本。如果出现了写入失效,就用页的副本来还原这个页,然后再应用 redo log。
这个页的副本就是 double write,InnoDB 的双写技术。通过它实现了数据页的可靠性。跟 redo log 一样,double write 由两部分组成,一部分是内存的 double write,一个部分是磁盘上的 double write。因为 double write 是顺序写入的,不会带来很大的开销。在默认情况下,所有的表共享一个系统表空间,这个文件会越来越大,而且它的空间不会收缩。
update user set name = 'penyuyan' where id=1;
1、事务开始,从内存或磁盘取到这条数据,返回给 Server 的执行器;
2、执行器修改这一行数据的值为 penyuyan;
3、记录 name=qingshan 到 undo log;
4、记录 name=penyuyan 到 redo log;
5、调用存储引擎接口,在内存(Buffer Pool)中修改 name=penyuyan;
6、事务提交。
后台线程的主要作用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。后台线程分为:master thread,IO thread,purge thread,page cleaner thread。
master thread 负责刷新缓存数据到磁盘并协调调度其它后台进程。
IO thread 分为 insert buffer、log、read、write 进程。分别用来处理 insert buffer、重做日志、读写请求的 IO 回调。
purge thread 用来回收 undo 页。
page cleaner thread 用来刷新脏页。
除了 InnoDB 架构中的日志文件,MySQL 的 Server 层也有一个日志文件,叫做binlog,它可以被所有的存储引擎使用。
1、先查询到这条数据,如果有缓存,也会用到缓存。
2、把 name 改成盆鱼宴,然后调用引擎的 API 接口,写入这一行数据到内存,同时记录 redo log。这时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,可以随时提交。
3、执行器收到通知后记录 binlog,然后调用存储引擎接口,设置 redo log为 commit状态。
4、更新完成。
重点:
1、先记录到内存,再写日志文件。
2、记录 redo log 分为两个阶段。
3、存储引擎和 Server 记录不同的日志。
4、先记录 redo,再记录 binlog。
索引文件的结构:hash、二叉树、B树、B+树。
MySQL 的存储结构分为 5 级:表空间、段、簇、页、行。
一个表空间最多拥有 2^32 个页,默认情况下一个页的大小为 16KB,也就是说一个表空间最多存储 64TB 的数据。
操作系统和内存打交道,最小的单位是页 Page。文件系统的内存页通常是 4K。
本质上是个数组,往里面存元素时,先通过哈希函数求得一个int值,再想该int值转换为数组中对应的位置,然后将元素插入。哈希表的数组中其实都是存的k-v键值对,key是真正存放的数据,value一般是某个固定值。
哈希表可以完成索引的存储,每次在添加索引的时候需要计算指定列的哈希值,取模运算后计算出下标,将元素插入下标位置。
哈希索引的特点:
1、它的时间复杂度是 O(1),查询速度比较快。因为哈希索引里面的数据不是按顺序存储的,所以不能用于排序。
2、我们在查询数据的时候要根据键值计算哈希码,所以它只能支持等值查询(= IN),不支持范围查询(> < >= <= between and)。
如果字段重复值很多的时候,会出现大量的哈希冲突(采用拉链法解决),效率会降低。
哈希表在使用的时候,需要将全部的数据加载到内存,比较耗费内存空间,不是很合适。
平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。当插入数据次数过多时,旋转次数较多,影响性能
。
红黑树是基于AVL树的一个升级,损失了部分查询性能,来提升插入的性能,在红黑树中最低子树和最高子树之差小于2倍即可,在插入的时候,不需要进行N多次的旋转操作,而且还加入了变色的性能,来满足插入和查询性能的平衡。
索引的数据,是放在硬盘上的。当我们用树的结构来存储索引的时候,访问一个节点就要跟磁盘之间发生一次 IO。InnoDB 操作磁盘的最小的单位是一页,大小是 16K。那么,一个树的节点就是 16K 的大小。
如果我们一个节点只存一个键值+数据+引用,例如整形的字段,可能只用了十几个或者几十个字节,它远远达不到 16K 的容量,所以访问一个树节点,进行一次 IO 的时候,浪费了大量的空间。所以如果每个节点存储的数据太少,从索引中找到我们需要的数据,就要访问更多的节点,意味着跟磁盘交互次数就会过多。如果是机械硬盘时代,每次从磁盘读取数据需要 10ms 左右的寻址时间,交互次数越多,消耗的时间就越多。
解决方案是什么呢?
第一个就是让每个节点存储更多的数据。第二个,节点上的关键字的数量越多,我们的指针数也越多,也就是意味着可以有更多的分叉(我们把它叫做“路数”)。因为分叉数越多,树的深度就会减少(根节点是 0)。
前面的结论就是:二叉树以及N多的变种都不能支撑索引,原因是树的深度过深,导致IO次数变多,影响数据读取的效率
。所以此时需要用多叉树(B树)来实现索引。
1、所有键值分布在整棵树中
2、所有有可能在非叶子节点结束在关键字全集内做一次查找,性能逼近二分查找
3、每个节点最多拥有m个子树
4、根节点至少有2个子树
5、分支节点至少拥有m/2颗子树(除根节点也叶子节点之外都是分支节点)
6、所有叶子节点都在同一层、每个节点最多可以拥有m-1个key,并且以升序排列。
在上面的例子中,假设每个节点占用一个磁盘块,一个节点上有两个升序排列的关键字和是哪个指向子树根节点的指针,指针存储的是子节点所在磁盘块的两个地址。两个关键词划分成的三个范围域对应的是三个指针指向的子树的数据的范围域。以根节点为例,关键字为16和34,P1指针指向的子树的数据范围为小于16,P2指针指向的子树的数据范围为16-34,P3指针指向的子树的数据范围为大于34。
查找关键字过程:
1、根据根节点找到磁盘块1,读入内存(磁盘IO第一次)
2、比较关键字28在区间(16,34),找到磁盘块1的指针P2
3、根据P2指针找到磁盘块3,读入内存(磁盘IO第二次)
4、比较关键字28在区间(15,31),找到磁盘块3的指针P2
5、根据P2指针找到磁盘块8,读入内存(磁盘IO第三次)
6、在磁盘块8中的关键字列表中找到关键字28
B树的缺点:
1、每个节点都有key和data,而每个页存储空间是有限的,如果data比较大的话会导致每个节点存储的key数量变小
2、当存储的数据量很大的时候会导致深度较大,增大查询时磁盘IO次数,进而影响查询性能
1、B+树每个节点可以包含更多的节点,这样做的原因有两个:一是为了降低树的高度,二是将数据范围变成多个区间,区间越多,数据检索越快
2、非叶子节点存储key,叶子节点存储key和数据
3、叶子节点两两指针相互连接(符合磁盘的预读特性),顺序查询性能更高。
在B+树上有两个头指针,一个指向根节点,一个另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+树进行两种查找:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进随机查找。
Innodb,B+树叶子节点直接放置数据的示例:
Innodb是通过B+树结构对主键创建索引,然后叶子节点中存储记录,如果没有主键,那么会选择唯一键,如果没有唯一键,那么会生成一个6位的row_id来作为主键。
如果创建索引的键是其他字段,那么在叶子节点中存储的是该记录的主键,然后再通过主键索引来找到对应的记录,叫做回表。
MyISAM,B+树和Inondb相比,差异之处就在于索引和表数据是分开存储的,看个例子:
InnoDB索引适用场景:
1)经常更新的表,适合处理多重并发的更新请求。
2)支持事务。
3)可以从灾难中恢复(通过 bin-log 日志等)。
4)外键约束。只有他支持外键。
5)支持自动增加列属性 auto_increment。
在 InnoDB 中 B+ 树深度一般为 1-3 层,它就能满足千万级的数据存储。
InnoDB 中的 B+Tree 的特点:
1)它是 B Tree 的变种,B Tree 能解决的问题,它都能解决。B Tree 解决的两大问题是什么?(每个节点存储更多关键字;路数更多)
2)扫库、扫表能力更强(如果我们要对表进行全表扫描,只需要遍历叶子节点就可以了,不需要遍历整棵 B+Tree 拿到所有的数据)
3) B+Tree 的磁盘读写能力相对于 B Tree 来说更强(根节点和枝节点不保存数据区,所以一个节点可以保存更多的关键字,一次磁盘加载的关键字更多)
4)排序能力更强(因为叶子节点上有下一个数据区的指针,数据形成了链表)
5)效率更加稳定(B+Tree 永远是在叶子节点拿到数据,所以 IO 次数是稳定的)
每张 InnoDB 的表有两个文件(.frm 和.ibd),MyISAM 的表有三个文件(.frm、.MYD、.MYI)。
frm 是 MySQL 里面表结构定义的文件,不管你建表的时候选用任何一个存储引擎都会生成。
在 MyISAM 里面,另外有两个文件:一个是.MYD 文件,D 代表 Data,是 MyISAM 的数据文件,存放数据记录。一个是.MYI 文件,I 代表 Index,是 MyISAM 的索引文件,存放索引。
MyISAM 的 B+Tree 里面,叶子节点存储的是数据文件对应的磁盘地址。所以从索引文件.MYI 中找到键值后,会到数据文件.MYD 中获取相应的数据记录。
在 InnoDB 里面,它是以主键为索引来组织数据的存储的,所以索引文件和数据文件是同一个文件,都在.ibd 文件里面。在 InnoDB 的主键索引的叶子节点上,它直接存储了我们的数据。
聚集索引:索引键值的逻辑顺序跟表数据行的物理存储顺序是一致的。(比如字典的目录是按拼音排序的,内容也是按拼音排序的,按拼音排序的这种目录就叫聚集索引)。
在 InnoDB 里面,它组织数据的方式叫做叫做(聚集)索引组织表,主键索引是聚集索引,非主键都是非聚集索引。
InnoDB 中,主键索引和辅助索引是有一个主次之分的。辅助索引存储的是辅助索引和主键值。如果使用辅助索引查询,会根据主键值在主键索引中查询,最终取得数据。
如果一张表没有主键怎么办?
1、如果我们定义了主键(PRIMARY KEY),那么 InnoDB 会选择主键作为聚集索引。
2、如果没有显式定义主键,则 InnoDB 会选择第一个不包含有 NULL 值的唯一索引作为主键索引。
3、如果也没有这样的唯一索引,则 InnoDB 会选择内置 6 字节长的 ROWID 作为隐藏的聚集索引,它会随着行记录的写入而主键递增。
在 InnoDB 里面,索引类型有三种,普通索引、唯一索引(主键索引是特殊的唯一索引)、全文索引。
mysql索引的五种类型:主键索引、唯一索引、普通索引和全文索引、组合索引。
通过给字段添加索引,可以提高数据的读取速度,提高项目的并发能力和抗压能力。
普通索引:仅加速查询
唯一索引:加速查询 +列值唯一(可以有null)
主键索引:加速查询 +列值唯一(不可以有null)+ 表中只有一个
组合索引:多列值组成一个索引,专门用于组合搜索
,其效率大于索引合并
全文索引:对文本的内容进行分词
,进行搜索
PRIMARY KEY
,每个表只能有一个主键。主键一般情况可以设置为自增,主键自增时,插入数据时都是在最后追加,不会在表中间插入数据,索引方便维护。覆盖索引,简单来说select查询的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖
)用 like + % 就可以实现模糊匹配了,为什么还要全文索引?like + % 在文本比较少时是合适的,但是对于大量的文本数据检索,是不可想象的。全文索引在大量的数据面前,能比 like + % 快 N 倍,速度不是一个数量级,但是全文索引可能存在精度问题。
多列值组成一个索引,专门用于组合搜索
。select * from t1 where c1=1 and c2=2
能够使用该索引。查询语句select * from t1 where c1=1
也能够使用该索引。但是,查询语句select * from t1 where c2=2
不能够使用该索引,因为没有组合索引的引导列,即要想使用c2列进行查找,必需出现c1等于某值。列的离散度:count(distinct(column_name)) : count(*),列的全部不同值和所有数据行的比例。数据行数相同的情况下,分子越大,列的离散度就越高。
简单来说,如果列的重复值越多,离散度就越低,重复值越少,离散度就越高。
建立索引,要使用离散度(选择度)更高的字段
,否则B+Tree 里面的重复值太多,MySQL 的优化器发现走索引跟使用全表扫描差不了多少的时候,就算建了索引,也不一定会走索引。
比如在 user 表上面,给 name 和 phone 建立了一个联合索引:
ALTER TABLE user_innodb DROP INDEX comidx_name_phone;
ALTER TABLE user_innodb add INDEX comidx_name_phone (name,phone);
联合索引在 B+Tree 中是复合的数据结构,它是按照从左到右的顺序来建立搜索树的(name 在左边,phone 在右边)。
name 是有序的,phone 是无序的。当 name 相等的时候,phone 才是有序的。这个时候我们使用where name='青山' and phone = '136xx '
去查询数据的时候,B+Tree 会优先比较 name 来确定下一步应该搜索的方向,往左还是往右。如果 name相同的时候再比较 phone。但是如果查询条件没有 name,就不知道第一步应该查哪个节点,因为建立搜索树的时候 name 是第一个比较因子,所以用不到索引。
仍以上面的表为例:
EXPLAIN SELECT * FROM user_innodb WHERE name= '权亮' AND phone = '15204661800';
EXPLAIN SELECT * FROM user_innodb WHERE name= '权亮'
EXPLAIN SELECT * FROM user_innodb WHERE phone = '15204661800'
非主键索引,我们先通过索引找到主键索引的键值,再通过主键值查出索引里面没有的数据,它比基于主键索引的查询多扫描了一棵索引树,这个过程就叫回表。
例如:select * from user_innodb where name = 'qingshan'
;
在辅助索引里面,不管是单列索引还是联合索引,如果 select 的数据列只用从索引中就能够取得,不必从数据区中读取,这时候使用的索引就叫做覆盖索引,这样就避免了回表。
索引条件下推(Index Condition Pushdown),简称ICP。MySQL5.6新添加,用于优化数据的查询。
索引条件下推优化(Index Condition Pushdown (ICP) )是MySQL5.6添加的,用于优化数据查询。不使用索引条件下推优化时存储引擎通过索引检索到数据,然后返回给MySQL服务器,服务器然后判断数据是否符合条件。
当使用索引条件下推优化时,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器
。索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
开启 ICP:
set optimizer_switch='index_condition_pushdown=on';
一个 SQL 语句是否使用索引,跟数据库版本、数据量、数据选择度都有关系。其实,用不用索引,最终都是优化器说了算。
优化器是基于 cost 开销,它不是基于规则,也不是基于语义。怎么样开销小就怎么来。
索引在插入新的值的时候,为了维护索引的有序性,必须要维护,在维护索引的时候需要需要分以下集中情况:
尽量使用自增主键作为索引
。
这两种日志是存储引擎层面的日志
。
Redo日志,innodb存储引擎的日志文件
。Undo Log是为了实现事务的原子性,在MySQL数据库InnoDB存储引擎中
,还用Undo Log来实现多版本并发控制。注意:undo log是逻辑日志,可以理解为:
当delete一条记录时,undo log中会记录一条对应的insert记录
当insert一条记录时,undo log中会记录一条对应的delete记录
当update一条记录时,它记录一条对应相反的update记录
binlog是Server层面的日志
,主要做mysql功能层面的事情。
binlog与redo日志的区别:
redo是innodb独有的
,binlog是所有引擎都可以使用的redo是循环写的,空间会用完
,binlog是可以追加写的,不会覆盖之前的日志信息 Binlog中会记录所有的逻辑,并且采用追加写的方式。一般在企业中数据库会有备份系统(用于应付数据丢失等情况),可以定期执行备份,备份的周期可以自己设置。
恢复数据的过程:
Redo log为什么两阶段提交?
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的 计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一 个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
相对其他数据库而言,MySQL的锁机制比较简单,其最 显著的特点是不同的存储引擎支持不同的锁机制。比如:
1. MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);
2. InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
表级锁是mysql锁中粒度最大的一种锁,表示当前的操作对整张表加锁,资源开销比行锁少,不会出现死锁的情况,但是发生锁冲突的概率很大。
该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。表锁被大部分的mysql引擎支持,MyISAM和InnoDB都支持表级锁。
MyISAM只支持表锁,因此性能相对Innodb来说相对降低,而Innodb支持表锁和行锁。
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
行锁的是mysql锁中粒度最小的一种锁,因为锁的粒度很小,所以发生资源争抢的概率也最小,并发性能最大,但是也会造成死锁,每次加锁和释放锁的开销也会变大。
主要是Innodb使用行锁,Innodb也是mysql在5.5.5版本之后默认使用的存储引擎。
开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特点来说哪种锁更合适。
仅从锁的角度 来说:表级锁更适合于以查询为主
,只有少量按索引条件更新数据的应用,如Web应用;而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有 并发查询的应用
,如一些在线事务处理(OLTP)系统。
OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。
OLTP 系统强调数据库内存效率,强调内存各种指标的命令率,强调绑定变量,强调并发操作;OLAP 系统则强调数据分析,强调SQL执行市场,强调磁盘I/O,强调分区等。
共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
示例:若事务A对数据对象1加上S锁,则事务A可以读数据对象1但不能修改,其他事务只能再对数据对象1加S锁,而不能加X锁,直到事务A释放数据对象1上的S锁。这保证了其他事务可以读数据对象1,但在事务A释放数据对象1上的S锁之前不能对数据对象1做任何修改。
排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁。
示例:若事务A对数据对象1加上X锁,事务A可以读数据对象1也可以修改数据对象1,其他事务不能再对数据对象1加任何锁,直到事务A释放数据对象1上的锁。这保证了其他事务在事务A释放数据对象1上的锁之前不能再读取和修改数据对象1。
意向共享锁(IS):事务想要在获得表中某些记录的共享锁,需要在表上先加意向共享锁。
意向互斥锁(IX):事务想要在获得表中某些记录的互斥锁,需要在表上先加意向互斥锁。
意向共享锁和意向排它锁总称为意向锁。意向锁的出现是为了支持Innodb支持多粒度锁。
意向锁是表级别锁。
当我们需要给一个加表锁的时候,我们需要根据意向锁去判断表中有没有数据行被锁定,以确定是否能加成功。如果意向锁是行锁,那么我们就得遍历表中所有数据行来判断。如果意向锁是表锁,则我们直接判断一次就知道表中是否有数据行被锁定了。所以说将意向锁设置成表级别的锁的性能比行锁高的多。
有了意向锁之后,前面例子中的事务A在申请行锁(写锁)之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后事务B申请表的写锁时会被阻塞。
意向锁的作用就是:当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。
乐观锁不是数据库自带的,需要我们自己去实现。示例:
- SELECT data AS old_data, version AS old_version FROM …;
- 根据获取的数据进行业务操作,得到new_data和new_version
- UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
// 乐观锁获取成功,操作完成
} else {
// 乐观锁获取失败,回滚并重试
}
乐观锁的优点:乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。
乐观锁的缺点:乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,应该充分考虑到这些情况出现的可能性,并进行相应调整(如将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。
总结:读用乐观锁,写用悲观锁
。
实现悲观锁时,必须先使用set autocommit=0;
关闭mysql的autoCommit属性,因为查询出数据之后就要将该数据锁定。
悲观锁的使用示例:
关闭自动提交后,我们需要手动开启事务。
//1.开始事务
begin; 或者 start transaction;
//2.查询出商品信息,然后通过for update锁定数据防止其他事务修改
select status from t_goods where id=1 for update;
//3.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//4.修改商品status为2
update t_goods set status=2;
//5.提交事务
commit; --执行完毕,提交事务
在第2步我们将数据查询出来后直接加上排它锁(X)锁,防止别的事务来修改事务1,直到我们commit后,才释放了排它锁。
悲观锁的优点:保证了数据处理时的安全性。
悲观锁的缺点:加锁造成了开销增加,并且增加了死锁的机会。降低了并发性。
接下来三种锁(间隙锁、记录锁、临键锁)都是innodb的行锁,前面我们说过行锁是基于索引实现的,一旦加锁操作没有操作在索引上,就会退化成表锁。
间隙锁,作用于非唯一索引上,主要目的,就是为了防止其他事务在间隔中插入数据,以导致“不可重复读”。
如果把事务的隔离级别降级为读提交(Read Committed, RC),间隙锁则会自动失效。
记录锁,它封锁索引记录,作用于唯一索引上。
临键锁,作用于非唯一索引上,是记录锁与间隙锁的组合。
死锁是指两个或两个以上事务在执行过程中因争抢锁资源而造成的互相等待的现象。
如何解决死锁?
1.等待事务超时,主动回滚。
2.进行死锁检查,主动回滚某条事务,让别的事务能继续走下去。
MySQL的表级锁有两种模式:表共享读锁(Table Read Lock)
和表独占写锁(Table Write Lock)
。
对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM表的写操作,则会阻塞其他用户对同一表的读和写操作;MyISAM表的读操作与写操作之间,以及写操作之间是串行的。
假设有以下数据:
CREATE TABLE `mylock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`NAME` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('1', 'a');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('2', 'b');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('3', 'c');
INSERT INTO `mylock` (`id`, `NAME`) VALUES ('4', 'd');
lock table mylock write
。加上该写锁之后,该session可以正常操作,别的session对该表的查询会阻塞。只有释放该锁:unlock tables
后,别的session查询才能正常进行。lock table mylock read
后,session1可以查询该表记录,session1不能查询没有锁定的表,session1插入或者更新表会提示错误。unlock tables
后,session2获得锁,更新成功。 MyISAM在执行查询语句之前,会自动给涉及的所有表加读锁,在执行更新操作前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要使用命令来显式加锁
。
lock table mylock read local
获取表的read local锁定,session1不能对表进行更新或者插入操作,session1不能查询没有锁定的表,session1不能访问其他session插入的记录。此时session2可以查询该表的记录,session2可以进行插入操作,但是更新会阻塞,。unlock tables
释放锁资源,session1可以查看session2插入的记录,session2获取锁,更新操作完成。可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺。
mysql> show status like 'table%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 352 |
| Table_locks_waited | 2 |
+-----------------------+-------+
如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。
事务是由一组SQL语句组成的逻辑处理单元,事务具有4属性,通常称为事务的ACID属性:原子性、一致性、隔离性、持久性。
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多用户的并发操作,但与此同时,会带来一些问题:
上述出现的问题都是数据库读一致性的问题,可以通过事务的隔离机制来进行保证。
数据库的事务隔离越严格,并发副作用就越小,但付出的代价也就越大,因为事务隔离本质上就是使事务在一定程度上串行化,需要根据具体的业务需求来决定使用哪种隔离级别。
| | 脏读 | 不可重复读 | 幻读 |
| :--------------: | :--: | :--------: | :--: |
| read uncommitted | √ | √ | √ |
| read committed | | √ | √ |
| repeatable read | | | √ |
| serializable | | | |
可以通过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺情况:
mysql> show status like 'innodb_row_lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 18702 |
| Innodb_row_lock_time_avg | 18702 |
| Innodb_row_lock_time_max | 18702 |
| Innodb_row_lock_waits | 1 |
+-------------------------------+-------+
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高。
InnoDB的行锁模式及加锁方法
mysql InnoDB引擎默认的修改数据语句:update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型
。
如果加排他锁可以使用select …for update语句,加共享锁可以使用select … lock in share mode语句。
所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select …from…查询数据,因为普通查询没有任何锁机制。
InnoDB行锁是通过给索引上的索引项加锁来实现的
,这一点MySQL与Oracle不同,Oracle是通过在数据块中对相应数据行加锁来实现的。
InnoDB这种行锁实现特点意味着:InnoDB只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
。
create table tab_no_index(id int,name varchar(10)) engine=innodb;
insert into tab_no_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');
session1可以通过select * from tab_no_index where id = 1 for update
命令,只给一行加了排他锁,但是session2在请求其他行的排他锁的时候,会出现锁等待。原因是在没有索引的情况下,innodb只能使用表锁。
create table tab_with_index(id int,name varchar(10)) engine=innodb;
alter table tab_with_index add index id(id);
insert into tab_with_index values(1,'1'),(2,'2'),(3,'3'),(4,'4');
此时当一个session对一个行加锁时,不影响另一个session对别的行的操作。
在了解InnoDB锁特性后,用户可以通过设计和SQL调整等措施减少锁冲突和死锁,包括:
事务是数据处理的最小操作单元,是一组不可再分割的操作集合,这个操作单元里的一系列操作要么都成功,要么都失败。/事务(Transaction)是一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。
事务最主要的目的是为了数据一致性
。
可以通过set session autoCommit = on/off
来设置mysql事务是否自动开启。如果我们设置autoCommit为off的时候,需要手动开启mysql事务。
begin;start transaction;----开启事务(2选1)
通过 commit;或者 rollback;设置事务提交或者回滚;
不同的数据库采用的隔离级别也会不一样。oracle采用的提交读(RC),而mysql默认的存储引擎innodb采用的是可重复读(RR)。
在RC级别下,所有的读取都是不加锁的,只有增删改的情况会加锁。
不可重复读和脏读的区别就是 读到的修改的数据是否提交
。 通过行锁算法可以知道,在读取id=1的数据时就可以给这条数据加上锁,这样就能避免在对这条数据进行其他操作(update/delete),但是insert还能继续操作,这样只能避免了不可重复读却无法避免幻读。
可以通过临键锁来解决幻读问题。但这是采用的悲观锁机制,不可避免的降低了性能,这是mysql不能接受的,在mysql中采用了以乐观锁为基础的MVCC(多版本并发控制)来解决幻读。
将数据库扩展成主从复制模式,将读操作和写操作分离开来,多台数据库分摊请求,从而减少单库的访问压力,进而应用得到优化。
为什么需要主从复制?
使用主从复制,让主库负责写,从库负责读,这样,即使主库出现了锁表的情景,通过读从库也可以保证业务的正常运作
。 MySQL主从复制是指数据可以从一个MySQL数据库服务器主节点复制到一个或多个从节点。MySQL 默认采用异步复制方式
,这样从节点不用一直访问主服务器来更新自己的数据,数据的更新可以在远程连接上进行,从节点可以复制主数据库中的所有数据库或者特定的数据库,或者特定的表。
也就是说:
从库会生成两个线程,一个I/O线程,一个SQL线程;
I/O线程会去请求主库的binlog,并将得到的binlog写到本地的relay-log(中继日志)文件中;
主库会生成一个log dump线程,用来给从库I/O线程传binlog;
SQL线程,会读取relay log文件中的日志,并解析成sql语句逐一执行;
简单来说:
1、master将操作语句记录到binlog日志中,然后授予slave远程连接的权限(master一定要开启binlog二进制日志功能;通常为了数据安全考虑,slave也开启binlog功能)。
2、slave开启两个线程:IO线程和SQL线程。其中:IO线程负责读取master的binlog内容到中继日志relay log里;SQL线程负责从relay log日志里读出binlog内容,并更新到slave的数据库里,这样就能保证slave数据和master数据保持一致了。
3、Mysql复制至少需要两个Mysql的服务,当然Mysql服务可以分布在不同的服务器上,也可以在一台服务器上启动多个服务。
4、Mysql复制最好确保master和slave服务器上的Mysql版本相同(如果不能满足版本一致,那么要保证master主节点的版本低于slave从节点的版本)。
5、master和slave两节点间时间需同步。
主从复制的具体步骤:
有以下五种形式:一主一从、主主复制、一主多从、多主一从、联级复制。
mysql的主从复制都是单线程的操作,主库对所有DDL和DML产生的日志写进binlog,由于binlog是顺序写,所以效率很高,slave的sql thread线程将主库的DDL和DML操作事件在slave中重放。DML和DDL的IO操作是随机的,不是顺序,所以成本要高很多,另一方面,由于sql thread也是单线程的,当主库的并发较高时,产生的DML数量超过slave的SQL thread所能处理的速度,或者当slave中有大型query语句产生了锁等待,那么延时就产生了。
解决方案:
1、业务的持久化层的实现采用分库架构,mysql服务可平行扩展,分散压力。
2、单个库读写分离,一主多从,主写从读,分散压力。这样从库压力比主库高,保护主库。
3、服务的基础架构在业务和mysql之间加入memcache或者redis的cache层。降低mysql的读压力。
4、不同业务的mysql物理上放在不同机器,分散压力。
5、使用比主库更好的硬件设备作为slave,mysql压力小,延迟自然会变小。
6、使用更加强劲的硬件设备
MySQL 5.7可以使用并行复制原理解决复制延迟问题。
两个虚拟机:ip:192.168.2.21(主) ip:192.168.2.22(从)。
# 创建用户
create user 'repl'@'%' identified by 'repl';
# 授权,只授予复制和客户端访问权限
grant replication slave,replication client on *.* to 'repl'@'%' identified by 'repl';
log-bin = mysql-bin
log-bin-index = mysql-bin.index
binlog_format = mixed
server-id = 21
sync-binlog = 1
character-set-server = utf8
保存文件并重启主库:
service mysqld restart
配置说明:
log-bin:设置二进制日志文件的基本名;
log-bin-index:设置二进制日志索引文件名;
binlog_format:控制二进制日志格式,进而控制了复制类型,三个可选值
-STATEMENT:语句复制
-ROW:行复制
-MIXED:混和复制,默认选项
server-id:服务器设置唯一ID,默认为1,推荐取IP最后部分;
sync-binlog:默认为0,为保证不会丢失数据,需设置为1,用于强制每次提交事务时,同步二进制日志到磁盘上。
flush tables with read lock;
获取二进制日志的坐标:
show master status;
返回结果示例:
+------------------+----------+--------------+------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 | 120 | | | |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)
备份数据:
# 针对事务性引擎
mysqldump -uroot -ptiger --all-database -e --single-transaction --flush-logs --max_allowed_packet=1048576 --net_buffer_length=16384 > /data/all_db.sql
# 针对 MyISAM 引擎,或多引擎混合的数据库
mysqldump -uroot --all-database -e -l --flush-logs --max_allowed_packet=1048576 --net_buffer_length=16384 > /data/all_db.sql
恢复主库的写操作:
unlock tables;
log-bin = mysql-bin
binlog_format = mixed
log-slave-updates = 0
server-id = 22
relay-log = mysql-relay-bin
relay-log-index = mysql-relay-bin.index
read-only = 1
slave_net_timeout = 10
保存文件并重启从库:
service mysqld restart
配置说明:
log-slave-updates:控制 slave 上的更新是否写入二进制日志,默认为0;若 slave 只作为从服务器,则不必启用;若 slave 作为其他服务器的 master,则需启用,启用时需和 log-bin、binlog-format 一起使用,这样 slave 从主库读取日志并重做,然后记录到自己的二进制日志中;
relay-log:设置中继日志文件基本名;
relay-log-index:设置中继日志索引文件名;
read-only:设置 slave 为只读,但具有super权限的用户仍然可写;
slave_net_timeout:设置网络超时时间,即多长时间测试一下主从是否连接,默认为3600秒,即1小时,这个值在生产环境过大,我们将其修改为10秒,即若主从中断10秒,则触发重新连接动作。
mysql -uroot -p < /data/all_db.sql
change master to
master_host='192.168.2.21',
master_user='repl',
master_password='repl',
master_port=3306,
master_log_file='mysql-bin.000001',
master_log_pos=120;
start slave;
查看从服务器复制功能状态:
show slave status
MySQL读写分离基本原理是让master数据库处理写操作,slave数据库处理读操作。master将写操作的变更同步到各个slave节点。
MySQL读写分离能提高系统性能的原因在于:
1、物理服务器增加,机器处理能力提升。拿硬件换性能。
2、主从只负责各自的读和写,极大程度缓解X锁和S锁争用。
3、slave可以配置myiasm引擎,提升查询性能以及节约系统开销。
4、master直接写是并发的,slave通过主库发送来的binlog恢复数据是异步。
5、slave可以单独设置一些参数来提升其读的性能。
6、增加冗余,提高可用性。