mysql性能的处理方式:
遗忘的概念:
一、Mysql的结构
1.mysql中数据库与数据库实例的区别
2.mysql数据库的组成
3.innodb的介绍
3.1innodb的架构
二、sql的执行过程
2.1查询sql的执行过程
2.2修改sql的执行过程
2.3change buffer
2.4查询中的排序算法(临时表)
三、数据库的事务
四、索引
五、数据库锁
六、表空间
七、性能分析
八、主备一致和读写分离
九、join语句的执行过程
十、临时表的解释
十一、分表
mysql性能的处理方式:
异步(日志记录),备份(从库),版本号(事务),标识(锁),多线程(链接池),批量处理(批量排序按索引查询),内存(各种buffer),排序(索引),尽可能的记录标识(异常重启之后根据标识判断分情况还原处理)
遗忘的概念:
1.锁
全局锁:是给整个库加锁
表锁:给整个表加锁
行锁:给扫描到的所有行加锁(一个没有索引的update会给整个表现有的行加锁)
行锁加锁和释放的原则:
读已提交隔离级别下
select * from t where d = 5 for update (d列没索引)(就算d列有索引根据扫描的过程也可能扫描到多行)
sql执行过程中:扫描全表,这时扫描到的行都加锁 扫描到的间歇也加间歇锁 就是next_key lock
sql执行完之后:不满足条件的行 d != 5 的行 next_key lock都会被去掉
d = 5 的行还是得等到事务提交时行锁才释放
间歇锁:给扫描到的索引区间加锁
行锁用来锁定涉及到的行(保证一致性),间歇锁用来锁定涉及到的区间(防止幻读)可以根据业务场景反推为什么这么用锁
2.mvcc处理事务,是根据版本号对比处理,不一样的事务加版本号的时机不一样
3.事务的隔离级别是就当前事务而言的
4. MVCC控制事务的读取的可见性,锁是用来控制读写造成的冲突,锁和mvcc共同实现事务的效果
5.索引是一种数据结构,数据行都在主键索引下边,普通索引下是主键id
数据行 数据页的概念
6.可重复读级别下会触发"间歇锁"
7.只有当前读才有幻读的问题 有了间歇锁当前读也不会出现幻读 因为数据insert不了
普通的读mvcc就能解决,当前读需要next_key lock解决
8.可重复读就解决了幻度,串行化是通过表锁解决的
三范式 表设计 字段属性选用 索引设计
了解HBASE
分库 分表的利弊
一、Mysql的结构
1.mysql中数据库与数据库实例的区别
数据库是文件的集合,是依照某种数据模型组织起来存放于二级存储器中的数据的集合
数据库实例是程序,所有对数据库的操作必须在数据库实例下进行
2.mysql数据库的组成
连接池、管理服务和工具的组件、sql接口组件、查询分析器、优化器、缓冲组件、存储引擎、物理文件
存储引擎:是基于底层物理结构的实现,是基于表的不是数据库
3.innodb的介绍
特点:支持行锁设计、mvcc、外键、提供一致性非锁定读、能有效利用内存和cpu
3.1innodb的架构
包括:多个后台线程、和内存池(缓存)
后台线程保证内存池中的数据和缓存中的数据一致,将缓存中修改过的数据刷新到数据库,同时将数据最新的数据同步到缓存
3.1.1后台线程
Master Thread 将缓存池中的数据异步刷新到磁盘,保证数据一致性
IO Thread innodb中大量使用AIO来操作数据库读写,write、read、insertBuffer、logThread
Purge Thread
3.1.2innodb内存
innodb的缓存
为了弥补磁盘操作较慢的问题,innodb对数据库加了一步缓存操作
1.读取数据,在数据库读取页的操作时,首先将从磁盘读到的页放到缓存池当中,这个过程称将页“FIX”在缓存池中,下次读取相同的页时先判断该页是否在缓存池中。若在缓存池中则命中直接读取该页,若没有则从磁盘中读取。
2.修改数据,首先修改在缓存池中的页,然后再以一定的频率刷到磁盘上,不是每次更新都会触发缓存和磁盘的同步,而是通过一种Checkpoint的机制。
连接池
HikariCP:目前速度最快的连接池,优化了代理和拦截器,简化了代码 新增了fastList,适合做监控
Durid:经过阿里大规模数据的检验比较可靠
DBCP/C3P0速度比价慢
按照效率排序的话,count(字段) 二、sql的执行过程 2.1查询sql的执行过程 连接器(连接数据库)、(缓存执行请求先走缓存(8.0之前))、分析器(分析请求类型、表、列 检查sql的正确性)、优化器(选择高效率的执行策略)、执行器(调用存储引擎的接口获取数据) 2.2修改sql的执行过程 update T set c=c+1 where id = 2 数据表使用了innodb 同样是经历 连接器 缓存 分析器 优化器 执行器 先从缓存或磁盘中取出id=2的数据,让后将取出的c的值加一 将修改后的这条数据更新到innodb的内存中同时将更新的内容更新到redo日志中,此时redo是prepare状态,然后告知执行器执行完了,随时可以提交事务 然后执行器生成这波操作的binlog日志),当当前这个事务提交后redo log和binlog同时完成写入 这里redo log 和 binlog是分两阶段提交的,先是准备后是提交,两个日志要么都写入成功要么都写入不成功 redo log 和 binlog的区别: redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。 redo log是物理日志(保证事务的持久性),记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。 redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。 Binlog有两种模式,statement 格式的话是记sql语句, row格式会记录行的内容,记两条,更新前和更新后都有 测试:从redo log功能角度解释一下innodb的缓存 check point 的触发时机:redo log 要写满了、缓存不足、空闲时间、MySQL关闭 2.3change buffer 使用场景: 1.当要修改一条数据, 2.这条数据在内存中不存在 3.先将修改的操作记录在change buffer中 4.当再次读取这条数据时,先将数据读取到内存中再用change buffer中记录的逻辑来修改内存中的数据 如果内存中存在就直接修改内存了 唯一索引因为必须要读取数据判断数据是否冲突所以不适用这种场景,change buffer 就是为了减少磁盘的读取io rodo log是为了减少写io 思考问题点: 1.innodb中 缓存,change buffer,redo log ,磁盘的关系 2.change buffer的使用场景(普通索引,写操作大于读操作) 2.4查询中的排序算法(临时表) 核心:通过sort_buffer将order by的列存起来然后做排序 再根据排序好的列来取数据拼成结果集 查询的列很少 走内存临时表 select word from t where time <2020-05-06 order by word; 内存临时表的储存引擎不是innodb所以需要“位置"这种数据 如果有分页,会将符合条件的数据都查出然后计算需要返回多少行到多少行 如果where条件和要排序的列属于同一个索引那就不需要排序,因为拿到的数据就是有序的 查询的列很多就根据索引走磁盘 select city,name,age,id_card, address,time from t where city = "杭州" order by name; 大查询会不会把MySQL内存打爆? 不会 mysql采用的策略是“边读边发”,mysql引入netBuffer的概念,一块内存空间,每次将读取到的数据放入netBuffer中netBuffer满了之后调用网络接口将数据发送出去然后将netBuffer清空进入下一轮的读取,如果本地网络栈写满了就等待直到网络栈重新可写再次发送 三、数据库的事务 读未提交 读已提交 可重复读 可重复读是站在当前事务的角度来说的 事务的属性:acid 原子性 一致性 隔离性 持久性 事务解决的问题:脏读 不可重复读 幻读 四个隔离级别: 读未提交:可以读取到未交的数据 读已提交:可以读取到提交后的数据 可重复读:在A事务中对某条数据读取了两次,这两次中间B事务对该条数据做了修改,但是A事务两次读取到的还是一样的数据(MySQL默认的隔离级别) 串行化:读加读锁,写加写锁 我们来看看在不同的隔离级别下,事务 A 会有哪些不同的返回结果,也就是图里面 V1、V2、V3 的返回值分别是什么。 若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。 若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。 若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。 若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。 隔离级别的实现方式:视图(read-view)和锁、回滚日志 数据库里会创建一个视图,访问的时候以视图的逻辑为准 读未提交:直接返回记录上的最新值 读已提交:在SQL语句执行的时候创建 可重复读:在事务开始的时候创建 串行化:直接通过加锁的方式来避免并行访问,当读写锁冲突的时候,后一个事务等前一个事务执行完在执行 回滚日志: MySQL的每次变更都会记录一条对应的回滚操作,记录上的最新值可以通过回滚操作得到前一个 状态的值 当一个事务回滚时,可一个通过回滚操作恢复数据 当没有视图需要回滚日志时,回滚日志就会删除 事务的详解 请仔细看一遍:第八讲事务到底是隔离的还是不隔离的 MVCC一行数据只有一个记录不是有多个纪录,但是会记录当前最新的版本号,如果想看到之前的版本需要根据undo log 来计算 行的版本号和事务的id是一致的 可重复读的事务里有修改语句修改语句是当前读 MVCC适用于“可重复读”,思考 读未提交,读已提交,insert语句 是怎么处理可见性的 长事务对MySQL的影响? 一个事务的执行时间过长,在整个事务中回滚段一直未被清理,大量的回滚日志占用很多磁盘空间 同时还占用锁资源,极大加重了整个库的压力 幻读的概念 读到了“新插入的数据行” 同一个事务中两次查询第二次查到了新插入的数据行(普通的查询是“快照读”,“当前读”的情况下才会出现“幻读”) 最后一行出现了幻读 四、索引 InnoDB 的数据是保存在主键索引上的 平衡二叉树 B+树 https://www.toutiao.com/a6624750730471277059/ 索引覆盖 :想要查询的结果集就是索引不用回表 索引最左原则:ABC索引 适合 A AB ABC 也是适合 like "xx%" 索引下推 :联合索引 "name,age" select * from custom where name like "张%" and age = 10; 为什么主键自增要好? 1.自增起始数据小占用空间小 2.索引采用的是B+树,数据自增可以减少“分裂”和“合并” 分裂:当出现中间数据时,B+树会触发分裂,MySQL也会申请新的数据将原来数据页中分出的数据拷贝过去,这个过程比较消耗资源同时原来的数据页未被使用完浪费了空间 合并:当相邻的两个数据页删除了数据,可能触发合并是分裂的逆过程同样消耗性能 为什么B+树适合建索引? 1.B+树非叶子节点没有关键字指针只存有关键字,非叶子节点可以存放更多的数据,所有树的高度比较低,查询的速度更快 2.非叶子节点存放的都是索引,数据都存放在叶子节点,方便扫库也适合区间查询,像B树非叶子节点也存放有数据,找到具体的数据需要进行中序遍历才可以 普通索引和唯一索引在查询性能上的差异大不大? 不大 1.唯一索引在索引树上读到符合条件的数据就不再读取 2.普通索引是读取到符合条件的数据之后会接着读取下一条,直到读到不符合条件的数据为止 3.innodb数据的读取是以页为单位来读取的,一次会将一页数据从磁盘中全部读出一页默认大小16kb 所以差别不大 字符串索引的三个小技巧: 1.采用前缀索引,将字符串的前几个值作为索引,可以减少空间占用 2.像身份证号这种数据 前面的数字都相同后边几位不同,存储的时候可以倒着存储然后用前缀索引 3.将长字符串取hash值通过hash值建立索引 where条件中有函数不走索引: select * from tradelog where tradeid=110717;(tradeild列是varchar类型有索引,但是这条语句却没走索引) select * from tradelog where CAST(tradid AS signed int) = 110717;(因为tardeild是varchar类型所以优化器需要将110717转换为字符串类型,然后再执行,转换使用了函数所以不适用索引) 两个表的字符集不同,一个是 utf8,一个是 utf8mb4,所以做表关联查询的时候用不上关联字段的索引 两个表字符集不同在优化器会使用函数修改sql将关联的那一列的数据的字符集修改为一样的,因为使用了函数所以不走索引 五、数据库锁 全局锁:FTWRL将整个库设置成只读状态,增删改,建表,修改表的操作都不能执行,影响范围很大,同时也影响主从同步;如果是innodb可以使用事务(可重复读)也可以实现多次读是一样的数据 表锁: 显示加锁,lock tables T read/write 非显示加锁,MDL 当访问表时(增删改查)数据库默认加上表锁读锁,当对数据表有结构修改时默认加上表锁写锁,读锁之间不互斥可以有多个,读锁和读锁,写锁和读锁之间互斥 修改表结构导致锁表的问题 就是因为读写锁互斥导致的,写锁没有获取到会一直阻塞后边的请求,当写锁获取到后 执行了修改表结构的语句 后边的业务语句才能执行 如何安全的给表加字段? 1.执行alter table语句之前确定没有长事务 2.执行语句中加入等待时间,在等待时间内没有获取到写锁,先不执行 让业务语句执行; 行锁:给扫描到的所有行加锁,行锁是存储引擎层面的概念不同的存储引擎有不同的行锁实现方式,MyISAM没有行锁 事务中的行锁,在sql执行时创建在事务提交后释放 行锁死锁的例子: 如何解决死锁问题? 1.设置锁的等待时间 innodb_lock_wait_timeout 超过时间 第一个被锁住的线程要过 50s默认 才会超时退出,然后其他线程才有可能继续执行 2.设置死锁检测 参数 innodb_deadlock_detect 设置为 on 发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行 3.也可以从数据表的设计上避免,像进行账号金额的操作,将一个账号改为多个账号,每次需要处理余额可以随机选取一个账号来处理,最终所有的账号余额的和为总账号的余额,这样减少了行死锁的概率; 当前读的概念: 修改语句是当前读,是先查询再修改 即使一个修改语句在可重复读的事务里,也会去读取最新的已提交的数据 修改语句同时会给涉及的行加写锁,(没有事务的情况下)语句执行完锁释放 select语句后加for update也是同样的操作(当前读 加写锁) mysql是如何运用行锁、间歇锁解决幻读的? update t set c = 5 where d = 2; update语句会添加写锁 假如d列没有索引,就会触发全局扫描,mysql会给所有扫描到的所有行加锁 insert into t (id , c , d),(1,4,2); 新插入一条d=5的数据,新插入的数据没有被扫描到不会加锁(容易引发幻读) 间歇锁:将涉及到的行的区间给加锁(包括两端) 下图中间歇锁 锁定的区间有(-负无穷,0],(0,5],(5,10],(10,15],(15,20],(20,25],(25,正无穷] 有了间歇锁就能解决幻读的问题 间歇锁的两原则,两优化,一个bug 原则 1:加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。 原则 2:查找过程中访问到的对象才会加锁。 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。 优化 2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。 间歇锁锁住行的范围更大,更容易导致死锁 六、表空间 1.innodb中表数据的存储方式 表结构:MySQL8.0前是存放在单独的文件里,8.0之后是存储在系统数据表里 表数据:可以通过配置选择 一种是存储在共享表空间,一种是一张表一个文件的形式存储 为什么删除了表数据表空间大小没变? mysql中数据是以数据页的形式存储 当删除一行数据时,会将这个行标记为可复用 当删除也一页数据时会将这个页标记为可复用 所以删除数据后占用的表空间不会变 如果真正的想收缩表空间可以通过重建表的方式实现,重建的过程中会重建索引重新分配内存 命令:alter table A engine=InnoDB 这是一个DDL的过程全程不能用写和修改的操作 mysql5.7之后有online DDL的操作 重建表的过程中允许添加和修改,它会把修改和添加的操作记录到日志文件中,重建完成之后将日志里的逻辑执行一遍 MDL:表级锁 DML:指的是增删改查的操作 DDL:指的是修改表结构的操作 七、性能分析 一条简单的查询语句查询速度很慢 mysql> CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; select * from t where id=1; 1.等DML锁 show processlist 可以查看当前sql语句的执行情况 是表被锁住了,找到锁表的线程直接kill掉 2.等flush flush tables t; 本身flush执行速度很快,可是flush如果被其他事物阻塞了,flush就会阻塞新的查询语句 其他事务中对t表有操作,flush会等待其他事务执行完再对t表进行flush 这个时候还是通过 show processlist命令查看当前sql执行情况 3.等待行锁 select * from t where id = 2; select语句默认不加读锁 select * from t where id = 2lock in share mode;显式加读锁 当前的行在其他事务中有写锁,这条语句就会等待写锁释放才能读取 4.数据量很大也没有索引 5.一致性读,读取的是老版本的数据,其他事务做了大量修改,需要执行大量的回滚日志才能看到老数据; 短连接风暴 一般的短连接模式就是,连接数据库之后执行很少的sql后就断开,再次需要的时候再次连接 风险:一旦数据库处理得慢一些,连接数就会暴涨超过了max_connections其他业务就不能连接数据库 弊端:MySQL 建立连接的过程,成本是很高的。除了正常的网络连接三次握手外,还需要做登录权限判断和获得这个连接的数据读写权限。 处理:show prossicelist 查看空闲的连接直接kill掉 为设置wait timeout 慢查询性能问题 在 MySQL 中,会引发性能问题的慢查询,大体有以下三种可能: 索引没有设计好;创建索引,修改表结构会触犯表锁 但是现在执行 online DDL,同时可以采用在备份库中执行alter table 然后切换主备 再在另外一个库中执行alter table SQL 语句没写好; sql优化、行锁、表锁、flush MySQL 选错了索引。 可以强制使用某个索引 如何判断一个数据库还能否正常使用? select 1 判断进程是否正常 select * from t 判断查询是否正常 查看updateTime(类型timestamp)字段 判断修改是否正常 可以通过命令查看磁盘io时间,如果频繁出现超时就是有性能问题 库里的表越多连接是否就越慢? 否 连接的过程中服务端就三个功能:TCP握手,用户校验,获取权限 这三个功能点和表的数据量无关所以不会影响速度 客户端会有补全表名,补全库名的功能,这个功能需要需要构建本地哈希表 这个和表的数量有关 sql优化 1.查询一个大表没有索引 建立索引 2.关联查询的是一个冷数据大表没有索引 t2表数据行超过200万行,b字段没有索引 select * from t1 join t2 on (t1.b=t2.b) where t2.b>=1 and t2.b<=2000; 改为: create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb; insert into temp_t select * from t2 where b>=1 and b<=2000; select * from t1 join temp_t on (t1.b=temp_t.b); 大表索引占用空间较大,又是冷数据所以没必要建立索引 可以建立临时表,给临时表建立索引 让sql可以走BKA算法 3.保洁商家打查询案例BAK 查询封装商家基本信息和商家其他信息 crm_custom 商家基本信息表 custom_id crm_custom_sign 商家签约表 custom_id service_id 201 crm_custom_level 商家等级表 custom_id crm_custom_category_status 商家状态表 custom_id 其他表 原始sql: select * from crm_custom a left join crm_custom_sign b on a.custom_id = b.custom_id left join crm_custom_level c on a.custom_id = c.custom_id left join crm_custom_category_status d on a.custom_id = d.custom_id where b.service_id = 201 limit0,30; 一百多万数据得执行八个小时 优化后: customIds = select a.custom_id from crm_custom a left join crm_custom_sign b where a.custom_id = b.custom_id where b.service_id = 201 order by a.custom_id desc limit 0,30 (crm_custom_sign 的custom_id有索引) 这一步走了索引覆盖 customBeans= select * from crm_custom from custom_id in (customIds); 这一步走了主键索引而且参数是有序的 BKA for循环customBeans 根据customId去各个表中获取想要列 这一步走了普通索引 优化后这个流程十分钟 4.分库分表后使用临时表优化查询 t 表通f列做key分发到24个库上 select * from t where a > M order by b limit 0,100; 由于a不是key所以会去24个库分别执行这个sql 正常思路:在24个库执行完sql拿到 24*100 = 2400条数据 然后通过程序按照b字段进行排序,然后取出前100条 使用临时表的方式: 在24个库中执行sql取出2400条数据 选择一个库创建临时表 create temporaty t_temp 将查询到的2400放入临时表 在临时表上执行sql select * from t_temp where a>M order by b limit 0,100; 这种场景使用临时表的好处: 分库后如果数据量较大又有 join order by 这样的复杂查询 如果使用程序处理对内存 CPU要求很高 如果使用临时表能够直接使用mysql来处理 mysql本身有很好的优化方案 5.尽量使用普通索引不使用唯一索引 6.limit分页参数过大为什么很慢? 原因:select * from t limit 300000,10; 需要扫描 300000+10行 select * from t limit 0,10; 需要扫描10行 解决方法: 记录上次查询的最大id:a select * from t where id > a order by id asc limit 0,10; 八、主备一致和读写分离 A库:主库 B库:备库(m-s结构) 核心是通过binlog来实现主备一致 1.在备库B上通过change master命令,设置主库A的IP、端口、用户名、密码以及从哪个位置开始请求binlog日志,这个位置包含文件名和日志偏移量 2.在备库上执行startslave命令这时候会启动两个线程,io_thread 和 sql_thread,其中io_thread负责与主库建立连接 3.主库校验完用户名和密码之后通过备份库B传过来的位置,从本读取binlog发送给B 4.备份库B拿到binlog后开始写本地文件,叫"中转日志"relay log 5.sql_threda读取中转日志,解析日志里的命令然后执行 A和B库互为主备关系(双M结构) MySQL 在 binlog 中记录了这个命令第一次执行时所在实例的 server id 从节点 A 更新的事务,binlog 里面记的都是 A 的 server id; 传到节点 B 执行一次以后,节点 B 生成的 binlog 的 server id 也是 A 的 server id; 再传回给节点 A,A 判断到这个 server id 与自己的相同,就不会再处理这个日志。所以,死循环在这里就断掉了。 一般情况下将从库设置为only read,但是主备同步使用的是super 用户,只读对super用户无效 binlog记录日志的三种形式 1.statement 记录执行的sql 2.row 根据主键记录数据的变化 3.mixed是statement和row的综合(根据情况选择使用statement或者row) 三种方式的比较: 安全: delete from t where a = 1 and b > "2020.02.02" limit 1;(a有索引,b也有索引) 如果采用statement 同样一条sql在不一样的库中可能删除了不一样的行,因为走了不一样的索引 如果采用row就不会出现这样的问题 如果采用mixed数据库会根据安全情况主动选择row 性能: statement:只需要记录相关sql就好了 row:需要记录所有涉及到的行 如果一条sql涉及十万行就得记录十万行 主备延迟的原因 备库的同步在一段时间内完全被堵住 1.主库起了一个大事务(包括大表的DDL、or一个事务操作很多行) 2.备库起了一个长事务,长时间占用线程 未完全堵住延迟时间较长 1.主库是多线程,从库是单线程,从库跟不上更新的速度 2.还有就是一些主从复制的策略也会影响 在主从的基础上可以实现读写分离 如何解决读写分离中从库延迟的问题?(数据同步的延迟) 1.强制走主库 对应一致要求特别高的数据读写都走主库 2.sleep 读取从库的数据前先sleep一下保证数据已经同步完毕 3.判断主备无延迟方案 第一种方法:每次从库执行查询请求前,先判断 seconds_behind_master 是否已经等于 0。如果还不等于 0 ,那就必须等到这个参数变为 0 才能执行查询请求(不完全准 单位是秒) 第二种方法,对比位点确保主备无延迟:保证binlog同步完也都执行了 Master_Log_File 和 Read_Master_Log_Pos,表示的是读到的主库的最新位点; Relay_Master_Log_File 和 Exec_Master_Log_Pos,表示的是备库执行的最新位点。 如果 Master_Log_File 和 Relay_Master_Log_File、Read_Master_Log_Pos 和 Exec_Master_Log_Pos 这两组值完全相同,就表示接收到的日志已经同步完成。 第三种方法,对比 GTID 集合确保主备无延迟:确认binlog都同步完也都执行了 Auto_Position=1 ,表示这对主备关系使用了 GTID 协议。 Retrieved_Gtid_Set,是备库收到的所有日志的 GTID 集合; Executed_Gtid_Set,是备库所有已经执行完成的 GTID 集合。 如果这两个集合相同,也表示备库接收到的日志都已经同步完成。 (第二种、第三种方法比一种准 但是还不够 因为主库执行完的日志可能还没完全提交过来) 4.配合semi-sync 签收机制 事务提交的时候,主库把 binlog 发给从库; 从库收到 binlog 以后,发回给主库一个 ack,表示收到了; 主库收到这个 ack 以后,才能给客户端返回“事务完成”的确认。 判断主备无延迟方案 配合semi-sync 配合使用可以保证主从的一致 5.GTID方案 数据库开启GTID模式 等待最新的事务同步完再执行查询 九、join语句的执行过程 select * from t1 straight_join t2 on (t1.a=t2.a); t2 的a字段不是主键但是有索引 sql执行过程 1.从t1表中读取一行数据R 2.从数据行R中取出字段a到t2表中去找 3.找到符合条件的行,和R组成一行作为结果集的一部分 4.重复执行1到3知道t1表的结尾循环结束 分析: t1表是驱动表 t2是被驱动表 1.对t1表做了全表扫描 全表行数h1 2.而对每一行R,根据R中的a去t2表搜索走了索引,假如a在t2表中不重复,那么t2表业扫描了h1行 3.所以整个流程扫描了2h1行数据 上边这个sql中假如t1表有N行 t2表有M行 时间复杂度为 o(n) = N + N*2*log2M; N对时间复杂度的影响是主要的 所有需要小表驱动大表 select * from t1 straight_join t2 on (t1.a=t2.b); t2的b字段不是主键也没有索引 t1表N行,t2表M行 sql执行过程 最笨的思路:全局扫描t1表,逐行读取R,取出R中的a值,全局扫描t2表逐条取出R2将R2中的b值和a值比较符合则拼装结果集,这样t2表需要全局扫描N次 总共扫描的行为 N+N*M; mysql真正的思路: 1.全局扫描t1表将t1表全部放入线程级的内存join_buffer 2.全局扫描t2表,逐条和join_buffer中的数据做对比得出符合条件的数据 两张表都只扫描一次 总共扫描的行数是:N+M 如果t1表太大join_buffer一次装不下怎么办? 答:那就分多次装 每多装一次t2表就需要多做一次全局扫描,用t2表中的所有数据和每个join_buffer做对比 总共扫描的行:N+k*M 可以表示为:N + λ*N*M 所以N对行数的影响还是最大还是应该:小表驱动大表 如果不强制性指定驱动关系,优化器会自动设置小表驱动大表 进一步的优化BKA 十、临时表的解释 内存表的概念: 1.数据都保存在内存中,系统重启时数据会被清空但是表结构还在 2.内存表只能使用memory存储引擎 其他属性和正常的数据表没有区别 临时表的概念: 1.临时表是相对于事务而言的,一个临时表只对一个事务可见,两个事务中有临时表的名称相同是互不影响的, session 结束的时候,会自动删除临时表 2.临时表数据是写在磁盘上的,可以使用各种存储引擎 3.临时表可以与普通表同名。 4.session A 内有同名的临时表和普通表的时候,show create 语句,以及增删改查语句访问的是临时表。 5.show tables 命令不显示临时表 十一、分表 1.分表的key选取 1.按时间字段分表,方便扩张 2.按全局主键分表,方便查询 3.结合可以预估范围的字段具体分析,根据业务有的也可按地域属性划分 分区分表: CREATE TABLE `t` ( `ftime` datetime NOT NULL, `c` int(11) DEFAULT NULL, KEY (`ftime`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 PARTITION BY RANGE (YEAR(ftime)) (PARTITION p_2017 VALUES LESS THAN (2017) ENGINE = InnoDB, PARTITION p_2018 VALUES LESS THAN (2018) ENGINE = InnoDB, PARTITION p_2019 VALUES LESS THAN (2019) ENGINE = InnoDB, PARTITION p_others VALUES LESS THAN MAXVALUE ENGINE = InnoDB); insert into t values('2017-4-1',1),('2018-4-1',1); 我在表 t 中初始化插入了两行记录,按照定义的分区规则,这两行记录分别落在 p_2018 和 p_2019 这两个分区上。 可以看到,这个表包含了一个.frm 文件和 4 个.ibd 文件,每个分区对应一个.ibd 文件。也就是说: 对于引擎层来说,这是 4 个表; 对于 Server 层来说,这是 1 个表 分表后锁性能的分析: 间隙锁会被阻断 表锁也只能作用于扫描到的分区(有多个分区相当于有多个表) 分区的应用场景 1.较大的历史记录表,可以按时间分区 2.分区之后业务代码更简洁 分库分表 订单系统分库分表拆分案例 整体大的步骤: 1.原来的库保持不同,业务上双写 2.引入es同步数据库表数据 3.非key字段为条件的查询如何处理 1)实时性要求不高的走es查询 2)数据一致性要求高的走原来的全量的库