您的包裹“ MySQL灵魂十连” 待签收
Posted 脚本之家
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了您的包裹“ MySQL灵魂十连” 待签收相关的知识,希望对你有一定的参考价值。
SQL 语句执行流程
Server 层:
连接器:TCP 握手后服务器来验证登陆用户身份,A 用户创建连接后,管理员对 A 用户权限修改了也不会影响到已经创建的链接权限,必须重新登录。
查询缓存:查询后的结果存储位置,MySQL8.0 版本以后已经取消,因为查询缓存失效太频繁,得不偿失。
分析器:根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。
优化器:多种执行策略可实现目标,系统自动选择最优进行执行。
执行器:判断是否有权限,将最终任务提交到存储引擎。
存储引擎层
SQL执行顺序
BinLog、RedoLog、UndoLog
2.1 BinLog
主节点必须启用二进制日志,记录任何修改了数据库数据的事件。
从节点开启一个线程(I/O Thread)把自己扮演成 mysql 的客户端,通过 mysql 协议,请求主节点的二进制日志文件中的事件 。
主节点启动一个线程(dump Thread),检查自己二进制日志中的事件,跟对方请求的位置对比,如果不带请求位置参数,则主节点就会从第一个日志文件中的第一个事件一个一个发送给从节点。
从节点接收到主节点发送过来的数据把它放置到中继日志(Relay log)文件中。并记录该次请求到主节点的具体哪一个二进制日志文件内部的哪一个位置(主节点中的二进制文件会有多个)。
从节点启动另外一个线程(sql Thread ),把 Relay log 中的事件读取出来,并在本地再执行一次。
2.2 RedoLog
直接把账本翻出来,把这次赊的账加上去或者扣除掉。
先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。
1、 记录更新时,InnoDB 引擎就会先把记录写到 RedoLog(粉板)里面,并更新内存。同时,InnoDB 引擎会在空闲时将这个操作记录更新到磁盘里面。 2、 如果更新太多 RedoLog 处理不了的时候,需先将 RedoLog 部分数据写到磁盘,然后擦除 RedoLog 部分数据。RedoLog 类似转盘。
-
当在 2 之前崩溃时,重启恢复后发现没有 commit,回滚。备份恢复:没有binlog 。一致 -
当在 3 之前崩溃时,重启恢复发现虽没有 commit,但满足 prepare 和binlog 完整,所以重启后会自动 commit。备份:有 binlog。一致
-
redo log 是 InnoDB 引擎特有的 ;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。 -
redo log 是物理日志,记录的是在某个数据页上做了什么修改;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如给 ID=2 这一行的 c 字段加1。 -
redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。追加写是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
2.3 UndoLog
insert undo log:
MySQL 中的索引
B+ 树非叶子节点存储的只是索引,可以存储的更多。B+树比 B 树更加矮胖,IO 次数更少。
B+ 树叶子节点前后管理,更加方便范围查询。同时结果都在叶子节点,查询效率稳定。
B+树中更有利于对数据扫描,可以避免 B 树的回溯扫描。
唯一索引可以保证每一行数据的唯一性 ;
提高查询速度 ;
加速表与表的连接 ;
显著的减少查询中分组和排序的时间;
通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
创建跟维护都需要耗时 ;
创建索引时,需要对表加锁,在锁表的同时,可能会影响到其他的数据操作 ;
索引需要磁盘的空间进行存储,磁盘占用也很快;
当对表中的数据进行 CRUD 的时,也会触发索引的维护,而维护索引需要时间,可能会降低数据操作性能。
索引不是越多越好。索引太多,维护索引需要时间跟空间;
频繁更新的数据,不宜建索引;
数据量小的表没必要建立索引。
重复率小的列建议生成索引。因为重复数据少,索引树查询更有效率,等价基数越大越好;
数据具有唯一性,建议生成唯一性索引。在数据库的层面,保证数据正确性 ;
频繁 group by、order by 的列建议生成索引。可以大幅提高分组和排序效率 ;
经常用于查询条件的字段建议生成索引。通过索引查询,速度更快。
模糊搜索:左模糊或全模糊都会导致索引失效,比如'%a'和'%a%'。但是右模糊是可以利用索引的,比如'a%' 。
隐式类型转换:比如 select * from t where name = xxx , name 是字符串类型,但是没有加引号,所以是由 MySQL 隐式转换的,所以会让索引失效 3、当语句中带有 or的时候:比如 select * from t where name=‘sw’ or age=14
不符合联合索引的最左前缀匹配:(A,B,C)的联合索引,你只 where 了 C 或 B 或只有 B,C
SQL 事务隔离级别
原子性(Atomicity):把多个操作放到一个事务中,保证这些操作要么都成功,要么都不成功;
一致性(Consistency):理解成一串对数据进行操作的程序执行下来,不会对数据产生不好的影响,比如凭空产生,或消失;
隔离性(Isolation,又称独立性):隔离性的意思就是多个事务之间互相不干扰,即使是并发事务的情况下,他们只是两个并发执行没有交集,互不影响的东西;当然实现中,也不一定需要这么完整隔离性,即不一定需要这么的互不干扰,有时候还是允许有部分干扰的。所以 MySQL 可以支持 4 种事务隔离性;
持久性(Durability):当某个操作操作完毕了,那么结果就是这样了,并且这个操作会持久化到日志记录中。
ACID 的 C 着重强调单数据库事务操作时,要保证数据的完整和正确性,数据不会凭空消失跟增加。 理论中的 C 指的是对一个数据多个备份的读写一致性
脏读(dirty read):B 事务更改数据还未提交,A 事务已经看到并且用了。B 事务如果回滚,则 A 事务做错了;
不可重复读(non-repeatable read):不可重复读的重点是修改: 同样的条件, 你读取过的数据, 再次读取出来发现值不一样了,只需要锁住满足条件的记录 ;
幻读(phantom read):事务 A 先修改了某个表的所有纪录的状态字段为已处理,未提交;事务 B 也在此时新增了一条未处理的记录,并提交了;事务 A 随后查询记录,却发现有一条记录是未处理的造成幻读现象,幻读仅专指新插入的行。幻读会造成语义上的问题跟数据一致性问题;
在可重复读 RR 隔离级别下,普通查询是快照读,是不会看到别的事务插入的数据的。因此,幻读在当前读下才会出现。要用间隙锁解决此问题。
MySQL 中的锁
锁分类
MyISAM 中的锁
-
虽然 MySQL 支持表、页、行三级锁定,但 MyISAM 存储引擎只支持表锁。 所以 MyISAM 的加锁相对比较开销低,但数据操作的并发性能相对就不高。但如果写操作都是尾插入,那还是可以支持一定程度的读写并发 -
从 MyISAM 所支持的锁中也可以看出,MyISAM 是一个支持读读并发,但不支持通用读写并发,写写并发的数据库引擎,所以它更适合用于读多写少的应用场合,一般工程中也用的较少。
InnoDB 中的锁
行锁只能锁住行,如果在记录之间的间隙插入数据就无法解决了,因此MySQL 引入了间隙锁(Gap Lock)。间隙锁是左右开区间。间隙锁之间不会冲突。
间隙锁和行锁合称 NextKeyLock,每个 NextKeyLock 是前开后闭区间。
加锁的基本单位是 NextKeyLock,是前开后闭区间。
查找过程中访问到的对象才会加锁。
索引上的等值查询,给唯一索引加锁的时候,NextKeyLock 退化为行锁。
索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,NextKeyLock 退化为间隙锁。
唯一索引上的范围查询会访问到不满足条件的第一个值为止。
MVCC
不加锁的 select 就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即 MVCC。可以认为 MVCC 是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。
快照读就是 MVCC 思想在 MySQL 的具体非阻塞读功能实现,MVCC 的目的就是为了实现读-写冲突不加锁,提高并发读写性能,而这个读指的就是快照读。
快照读就是 MySQL 为我们实现 MVCC 理想模型的其中一个具体非阻塞读功能。
MVCC + 悲观锁:MVCC 解决读写冲突,悲观锁解决写写冲突 MVCC + 乐观锁:MVCC 解决读写冲突,乐观锁解决写写冲突
MVCC的实现原理
DB_TRX_ID:6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务 ID;
DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment 里);
DB_ROW_ID:6byte,隐含的自增 ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引;
FLAG:一个删除 flag 隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除 flag 变了。
insert 时:InnoDB 为新插入的每一行保存当前系统版本号作为版本号。
select时:
InnoDB 只会查找版本早于当前事务版本的数据行(也就是行的系统版本号<=事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行在事务开始之前未被删除。
只有以上同时满足的记录,才能返回作为查询结果。
delete时:InnoDB 会为删除的每一行保存当前系统的版本号(事务的ID )作为删除标识.
update时:InnoDB 执行 update,实际上是新插入了一行记录,并保存其创建时间为当前事务的 ID,同时保存当前事务 ID 到要 update 的行的删除时间。
-
事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力。 -
在 RC 隔离级别下,是每个快照读都会生成并获取最新的 Read View;而在RR 隔离级别下,则是同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。
缓冲池(buffer pool)
存在的意义是加速查询
缓冲池(buffer pool) 是一种常见的降低磁盘访问 的机制;
缓冲池通常以页(page 16K)为单位缓存数据;
缓冲池的常见管理算法是 LRU,memcache,OS,InnoDB 都使用了这种算法;
InnoDB 对普通 LRU 进行了优化:将缓冲池分为老生代和新生代,入缓冲池的页,优先进入老生代,该页被访问,才进入新生代,以解决预读失效的问题页被访问。且在老生代停留时间超过配置阈值的,才进入新生代,以解决批量数据访问,大量热数据淘汰的问题。
table 瘦身
新建一个跟 A 表结构相同的表 B;
按照主键 ID 将 A 数据一行行读取同步到表 B;
用表 B 替换表 A 实现效果上的瘦身。
alter table A engine=InnoDB,慎重用,牛逼的 DBA 都用下面的开源工具。
推荐 Github:gh-ost
SQL Joins、统计、 随机查询
MyISAM 模式下把一个表的总行数存在了磁盘上,直接拿来用即可
InnoDB 引擎由于 MVCC 的原因,需要把数据读出来然后累计求和
性能来说,由好到坏:count(字段) < count(主键id) < count(1) ≈ count(*),尽量用 count(*)。
mysql> select word from words order by rand() limit 3;
mysql> select count(*) into @C from t;
set @Y1 = floor(@C * rand());
set @Y2 = floor(@C * rand());
set @Y3 = floor(@C * rand());
select * from t limit @Y1,1;
select * from t limit @Y2,1;
select * from t limit @Y3,1;
in 查询时首先查询子查询的表,然后将内表和外表做一个笛卡尔积,然后按照条件进行筛选。
子查询使用 exists,会先进行主查询,将查询到的每行数据循环带入子查询校验是否存在,过滤出整体的返回数据。
两表大小相当,in 和 exists 差别不大。内表大,用 exists 效率较高;内表小,用 in 效率较高。
查询用 not in 那么内外表都进行全表扫描,没有用到索引;而 not extsts 的子查询依然能用到表上的索引。not exists 都比 not in 要快。
MySQL 优化
1、合理建立覆盖索引:可以有效减少回表。 2、union,or,in都能命中索引,建议使用 in 3、负向条件(!=、<>、not in、not exists、not like 等) 索引不会使用索引,建议用in。 4、在列上进行运算或使用函数会使索引失效,从而进行全表扫描 5、小心隐式类型转换,原字符串用整型会触发 CAST 函数导致索引失效。原 int 用字符串则会走索引。 6、不建议使用%前缀模糊查询。 7、多表关联查询时,小表在前,大表在后。在 MySQL 中,执行 from 后的表关联查询是从左往右执行的(Oracle 相反),第一张表会涉及到全表扫描。 8、调整 Where 字句中的连接顺序,MySQL 采用从左往右,自上而下的顺序解析 where 子句。根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集。
先用慢查询日志定位具体需要优化的 sql;
使用 explain 执行计划查看索引使用情况 ;
重点关注(一般情况下根据这 4 列就能找到索引问题):key(查看有没有使用索引)、key_len(查看索引使用是否充分)、type(查看索引类型)、Extra(查看附加信息:排序、临时表、where 条件为 false 等);
根据上 1 步找出的索引问题优化 sql 5、再回到第 2 步。
尽量使用 TINYINT、SMALLINT、MEDIUM_INT 作为整数类型而非 INT,如果非负则加上 UNSIGNED 。
VARCHAR 的长度只分配真正需要的空间 。
尽量使用 TIMESTAMP 而非 DATETIME 。
单表不要有太多字段,建议在 20 以内。
避免使用 NULL 字段,很难查询优化且占用额外索引空间。字符串默认为''。
垂直分库:将应用分为若干模块,比如订单模块、用户模块、商品模块、支付模块等等。其实就是微服务的理念。
垂直分表:一般将不常用字段跟数据较大的字段做拆分。
水平分表:根据场景选择什么字段作分表字段,比如淘宝日订单 1000 万,用 userId 作分表字段,数据查询支持到最近 6 个月的订单,超过 6 个月的做归档处理,那么 6 个月的数据量就是 18 亿,分 1024 张表,每个表存 200W 数据,hash(userId)%100 找到对应表格。
ID生成器: 需要跨库全局唯一方便查询存储-检索数据,确保唯一性跟数字递增性。
支持 MySQL 协议(开发接入成本低)。
100% 支持事务(数据一致性实现简单、可靠)。
无限水平拓展(不必考虑分库分表),不停服务。
TiDB 支持和 MySQL 的互备。
遵循 jdbc 原则,学习成本低,强关系型,强一致性,不用担心主从配置,不用考虑分库分表,还可以无缝动态扩展。
适合:
原业务的 MySQL 的业务遇到单机容量或者性能瓶颈时,可以考虑使用 TiDB 无缝替换 MySQL。
大数据量下,MySQL 复杂查询很慢。
大数据量下,数据增长很快,接近单机处理的极限,不想分库分表或者使用数据库中间件等对业务侵入性较大、对业务有约束的 Sharding 方案。
大数据量下,有高并发实时写入、实时查询、实时统计分析的需求。
有分布式事务、多数据中心的数据 100% 强一致性、auto-failover 的高可用的需求。
单机 MySQL 能满足的场景也用不到 TiDB。
数据条数少于 5000w 的场景下通常用不到 TiDB,TiDB 是为大规模的数据场景设计的。
如果你的应用数据量小(所有数据千万级别行以下),且没有高可用、强一致性或者多数据中心复制等要求,那么就不适合使用 TiDB。
(完)
推荐阅读:
每日打卡赢积分兑换书籍入口
由于微信公众号近期改变了推送规则,如果你想如常看到我们的文章,可以时常点击文末右下角的「 在看 」;或者将 脚本之家 星标。
这样操作后,我们每次新的推送才能第一时间出现在你的订阅列表中~
以上是关于您的包裹“ MySQL灵魂十连” 待签收的主要内容,如果未能解决你的问题,请参考以下文章