[必须了解的mysql三大日志-binlog、redo log和undo log[]
https://segmentfault.com/a/1190000023827696
存储引擎层负责数据的存储和提取
不同存储引擎的表数据存取方式不同,支持的功能也不同,在后面的文章中,我们会讨论到引擎的选择。
不同的存储引擎共用一个 Server 层,也就是从连接器到执行器的部分。
第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:
mysql -h$ip -P$port -u$user -p
虽然密码也可以直接跟在 -p 后面写在命令行中,但这样可能会导致你的密码泄露。如果你连的是生产服务器,强烈建议你不要这么做。
连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份,这个时候用的就是你输入的用户名和密码。
这就意味着,一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建的连接才会使用新的权限设置。
连接完成后,如果你没有后续的动作,这个连接就处于空闲状态,你可以在 show processlist 命令中看到它。文本中这个图是 show processlist 的结果,其中的 Command 列显示为“Sleep”的这一行,就表示现在系统里面有一个空闲连接。
客户端如果太长时间没动静,连接器就会自动将它断开。这个时间是由参数 wait_timeout 控制的,默认值是 8 小时。
如果在连接被断开之后,客户端再次发送请求的话,就会收到一个错误提醒: Lost connection to MySQL server during query。这时候如果你要继续,就需要重连,然后再执行请求了。
数据库里面,长连接是指连接成功后,如果客户端持续有请求,则一直使用同一个连接。短连接则是指每次执行完很少的几次查询就断开连接,下次查询再重新建立一个。
建立连接的过程通常是比较复杂的,所以我建议你在使用中要尽量减少建立连接的动作,也就是尽量使用长连接。
但是全部使用长连接后,你可能会发现,有些时候 MySQL 占用内存涨得特别快,这是因为 MySQL 在执行过程中临时使用的内存是管理在连接对象里面的。这些资源会在连接断开的时候才释放。所以如果长连接累积下来,可能导致内存占用太大,被系统强行杀掉(OOM),从现象看就是 MySQL 异常重启了。
怎么解决这个问题呢?你可以考虑以下两种方案。
1.定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
2.如果你用的是 MySQL 5.7 或更新版本,可以在每次执行一个比较大的操作后,通过执行 mysql_reset_connection 来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。
连接建立完成后,你就可以执行 select 语句了。执行逻辑就会来到第二步:查询缓存。
MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。之前执行过的语句及其结果可能会以 key-value 对的形式,被直接缓存在内存中。key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。
如果语句不在查询缓存中,就会继续后面的执行阶段。执行完成后,执行结果会被存入查询缓存中。你可以看到,如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。
但是大多数情况下我会建议你不要使用查询缓存,为什么呢?因为查询缓存往往弊大于利。
查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。
好在 MySQL 也提供了这种“按需使用”的方式。你可以将参数 query_cache_type 设置成 DEMAND,这样对于默认的 SQL 语句都不使用查询缓存。而对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:
mysql> select SQL_CACHE * from T where ID=10;
需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。
如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析。
分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。
MySQL 从你输入的"select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T”识别成“表名 T”,把字符串“ID”识别成“列 ID”。
做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个 SQL 语句是否满足 MySQL 语法。
如果你的语句不对,就会收到“You have an error in your SQL syntax”的错误提醒,比如下面这个语句 select 少打了开头的字母“s”。
mysql> elect * from t where ID=1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'elect * from t where ID=1' at line 1
一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“use near”的内容。
经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。
优化器是在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序。比如你执行下面这样的语句,这个语句是执行两个表的 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。
这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一个方案。
优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。如果你还有一些疑问,比如优化器是怎么选择索引的,有没有可能选择错等等,没关系,我会在后面的文章中单独展开说明优化器的内容。
MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。
开始执行的时候,要先判断一下你对这个表 T 有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示 (在工程实现上,如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限)。
mysql> select * from T where ID=10;
ERROR 1142 (42000): SELECT command denied to user 'b'@'localhost' for table 'T'
如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。
比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的执行流程是这样的:
调用 InnoDB 引擎接口取这个表的第一行,判断 ID 值是不是 10,如果不是则跳过,如果是则将这行存在结果集中;
调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行。
执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。
至此,这个语句就执行完成了。
对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。
你会在数据库的慢查询日志中看到一个 rows_examined 的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。
在有些场景下,执行器调用一次,在引擎内部则扫描了多行,因此引擎扫描行数跟 rows_examined 并不是完全相同的。我们后面会专门有一篇文章来讲存储引擎的内部机制,里面会有详细的说明。
今天我给你介绍了 MySQL 的逻辑架构,希望你对一个 SQL 语句完整执行流程的各个阶段有了一个初步的印象。由于篇幅的限制,我只是用一个查询的例子将各个环节过了一遍。如果你还对每个环节的展开细节存有疑问,也不用担心,后续在实战章节中我还会再提到它们。
前面我们系统了解了一个查询语句的执行流程,并介绍了执行过程中涉及的处理模块。相信你还记得,一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块,最后到达存储引擎。
那么,一条更新语句的执行流程又是怎样的呢?
之前你可能经常听 DBA 同事说,MySQL 可以恢复到半个月内任意一秒的状态,惊叹的同时,你是不是心中也会不免会好奇,这是怎样做到的呢?
我们还是从一个表的一条更新语句说起,下面是这个表的创建语句,这个表有一个主键 ID 和一个整型字段 c:
mysql> create table T(ID int primary key, c int);
如果要将 ID=2 这一行的值加 1,SQL 语句就会这么写:
mysql> update T set c=c+1 where ID=2;
前面我有跟你介绍过 SQL 语句基本的执行链路,这里我再把那张图拿过来,你也可以先简单看看这个图回顾下。首先,可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIPWd2ui-1615554620605)(MySQL 实战45讲–笔记.assets/0d2070e8f84c4801adbfa03bda1f98d9.png)]
MySQL 的逻辑架构图
你执行语句前要先连接数据库,这是连接器的工作。
你执行语句前要先连接数据库,这是连接器的工作。
前面我们说过,在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。
接下来,分析器会通过词法和语法解析知道这是一条更新语句。优化器决定要使用 ID 这个索引。然后,执行器负责具体执行,找到这一行,然后更新。
与查询流程不一样的是,更新流程还涉及两个重要的日志模块,它们正是我们今天要讨论的主角:redo log(重做日志)和 binlog(归档日志)。如果接触 MySQL,那这两个词肯定是绕不过的,我后面的内容里也会不断地和你强调。不过话说回来,redo log 和 binlog 在设计上有很多有意思的地方,这些设计思路也可以用到你自己的程序里。
如果有人要赊账或者还账的话,掌柜一般有两种做法:
一种做法是直接把账本翻出来,把这次赊的账加上去或者扣除掉;
另一种做法是先在粉板上记下这次的账,等打烊以后再把账本翻出来核算。
在生意红火柜台很忙时,掌柜一定会选择后者,因为前者操作实在是太麻烦了。首先,你得找到这个人的赊账总额那条记录。你想想,密密麻麻几十页,掌柜要找到那个名字,可能还得带上老花镜慢慢找,找到之后再拿出算盘计算,最后再将结果写回到账本上。
这整个过程想想都麻烦。相比之下,还是先在粉板上记一下方便。你想想,如果掌柜没有粉板的帮助,每次记账都得翻账本,效率是不是低得让人难以忍受?
同样,在 MySQL 里也有这个问题,如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。
而粉板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘,也就是先写粉板,等不忙的时候再写账本。
具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。
如果今天赊账的不多,掌柜可以等打烊后再整理。但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。
与此类似,InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-znhTwMQg-1615554620609)(MySQL 实战45讲–笔记.assets/16a7950217b3f0f4ed02db5db59562a7.png)]
write pos 是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
要理解 crash-safe 这个概念,可以想想我们前面赊账记录的例子。只要赊账记录记在了粉板上或写在了账本上,之后即使掌柜忘记了,比如突然停业几天,恢复生意后依然可以通过账本和粉板上的数据明确赊账账目。
事务的性质(ACID):
SQL标准的事务隔离级别包括:
展开说明"可重复读"
在MYSQL中,实际上记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
什么时候删除回滚日志?
在不需要的时候才删除。系统会判断当没有事务再需要用到这些回滚日志时,回滚日志就会被删除。
尽量不要使用长事务
因为会导致大量占用存储空间。
MYSQL的事务启动方式有以下几种:
对于数据库的表而言,索引其实就是它的"目录"。
踩过坑:有人问我联合索引的技巧,回答的不是很好
总结:
1、覆盖索引:如果查询条件使用的是普通索引(或是联合索引的最左原则字段),查询结果是联合索引的字段或是主键,不用回表操作,直接返回结果,减少IO磁盘读写读取正行数据
2、最左前缀:联合索引的最左 N 个字段,也可以是字符串索引的最左 M 个字符
3、联合索引:根据创建联合索引的顺序,以最左原则进行where检索,比如(age,name)以age=1 或 age= 1 and name=‘张三’可以使用索引,单以name=‘张三’ 不会使用索引,考虑到存储空间的问题,还请根据业务需求,将查找频繁的数据进行靠左创建索引。
4、索引下推:like 'hello%’and age >10 检索,MySQL5.6版本之前,会对匹配的数据进行回表查询。5.6版本后,会先过滤掉age<10的数据,再进行回表查询,减少回表率,提升检索速度
全局锁主要用在逻辑备份过程中。对于全部是 InnoDB 引擎的库,我建议你选择使用–single-transaction 参数,对应用会更友好。
表锁一般是在数据库引擎不支持行锁的时候才会被用到的。如果你发现你的应用程序里有 lock tables 这样的语句,你需要追查一下,比较可能的情况是:
MDL 会直到事务提交才释放,在做表结构变更的时候,你一定要小心不要导致锁住线上查询和更新。
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。
出现死锁以后有两种策略:
如何解决热点行更新导致的性能问题?
1、如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关闭掉。一般不建议采用
2、控制并发度,对应相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。
3、将热更新的行数据拆分成逻辑上的多行来减少锁冲突,但是业务复杂度可能会大大提高。
innodb行级锁是通过锁索引记录实现的,如果更新的列没建索引是会锁住整个表的。
innodb支持RC和RR隔离级别实现是用的一致性视图(consistent read view)
事务在启动时会拍一个快照,这个快照是基于整个库的。
基于整个库的意思就是说一个事务内,整个库的修改对于该事务都是不可见的(对于快照读的情况)如果在事务内select表,另外的事务执行了DDL t表,根据发生时间,要嘛锁住要嘛报错(参考第六章)
事务是如何实现的MVCC呢?
(1)每个事务都有一个事务ID,叫做transaction id(严格递增)
(2)事务在启动时,找到已提交的最大事务ID记为up_limit_id。
(3)事务在更新一条语句时,比如id=1改为了id=2.会把id=1和该行之前的row trx_id写到undo log里,
并且在数据页上把id的值改为2,并且把修改这条语句的transaction id记在该行行头
(4)再定一个规矩,一个事务要查看一条数据时,必须先用该事务的up_limit_id与该行的transaction id做比对,
如果up_limit_id>=transaction id,那么可以看.
如果up_limit_id
什么是当前读,由于当前读都是先读后写,只能读当前的值,所以为当前读.会更新事务内的up_limit_id为该事务的transaction id
为什么rr能实现可重复读而rc不能,分两种情况
(1)快照读的情况下,rr不能更新事务内的up_limit_id,而rc每次会把up_limit_id更新为快照读之前最新已提交事务的transaction id,则rc不能可重复读
(2)当前读的情况下,rr是利用record lock+gap lock来实现的,而rc没有gap,所以rc不能可重复读
选择普通索引还是唯一索引?
对于查询过程来说:
(1) 普通索引,查到满足条件的第一个记录后,继续查找下一个记录,直到第一个不满足条件的记录
(2)唯一索引,由于索引唯一性,查到第一个不满足条件的记录后,停止检索
但是,两者的性能差距微乎其微。因为InnDB根据数据页来读写的。
对于更新过程来说:
概念:change buffer
当需要更新一个数据页:
(1)如果数据页在内存中就直接更新。
(2)如果不在内存中,在不影响数据一致性的前提下,InnoDB会将这些更新操作缓存在change buffer中。下次查询需要访问这个数据页的时候,将数据页读入内存,然后执行change buffer 中的与这个页有关的操作。
change buffer 是可以持久化的数据。在内存中有拷贝,也会被写入到磁盘上
purge:将change buffer 中操作应用到原数据页上,得到最新结果的过程中,也会执行purge 访问这个数据页会出发purge,系统后台线程定期purge,在数据库正常关闭的过程中,也会执行purge
唯一索引的更新不能使用change buffer
change buffer用的是buffer pool里的内存,change buffer的大小,可以通过参数innodb_change_buffer_max_size来动态设置。这个参数设置为50的时候,表示change buffer的大小最多只能占用buffer pool的50%。
将数据从磁盘读入内存涉及随机IO的访问,是数据库里面成本最高的操作之一。
change buffer 因为减少了随机磁盘访问,所以对更新性能的提升很明显。
change buffer使用场景
索引的选择和实践:
尽可能使用普通索引。
redo log主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。
1:MySQL抖一下是什么意思?
抖我认为就是不稳定的意思,一个SQL语句平时速度都挺快的,偶尔会慢一下且没啥规律,就是抖啦!
2:MySQL为啥会抖一下?
因为运行的不正常了,或者不稳定了,需要花费更多的资源处理别的事情,会使SQL语句的执行效率明显变慢。针对innoDB导致MySQL抖的原因,主要是InnoDB 会在后台刷脏页,而刷脏页的过程是要将内存页写入磁盘。所以,无论是你的查询语句在需要内存的时候可能要求淘汰一个脏页,还是由于刷脏页的逻辑会占用 IO 资源并可能影响到了你的更新语句,都可能是造成你从业务端感知MySQL“抖”了一下的原因。
3:MySQL抖一下有啥问题?
很明显系统不稳定,性能突然下降对业务端是很不友好的。
4:怎么让MySQL不抖?
设置合理参数配配置,尤其是设置 好innodb_io_capacity 的值,并且平时要多关注脏页比例,不要让它经常接近 75%
5:啥是脏页?
当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”。
按照这个定义感觉脏页是不可避免的,写的时候总会先写内存再写磁盘和有没有用WAL没啥关系?
6:啥是干净页?
内存数据写入到磁盘后,内存和磁盘上的数据页的内容就一致了,称为“干净页”。
7:脏页是咋产生的?
因为使用了WAL技术,这个技术会把数据库的随机写转化为顺序写,但副作用就是会产生脏页。
8:啥是随机写?为啥那么耗性能?
随机写我的理解是,这次写磁盘的那个扇区和上一次没啥关系,需要重新定位位置,机械运动是很慢的即使不是机械运动重新定位写磁盘的位置也是很耗时的。
9:啥是顺序写?
顺序写我的理解是,这次写磁盘那个扇区就在上一次的下一个位置,不需要重新定位写磁盘的位置速度当然会快一些。
10:WAL怎么把随机写转化为顺序写的?
写redolog是顺序写的,先写redolog等合适的时候再写磁盘,间接的将随机写变成了顺序写,性能确实会提高不少。
1:为啥删除了表的一半数据,表文文件大小没变化?
因为delete 命令其实只是把记录的位置,或者数据页标记为了“可复用”,但磁盘文件的大小是不会变的。也可以认为是一种逻辑删除,所以物理空间没有实际释放,只是标记为可复用,表文件的大小当然是不变的啦!
2:表的数据信息存在哪里?
表数据信息可能较小也可能巨大无比,她可以存储在共享表空间里,也可以单独存储在一个以.ibd为后缀的文件里,由参数innodb_file_per_table来控制,老师建议总是作为一个单独的文件来存储,这样非常容易管理,并且在不需要的时候,使用drop table命令也能直接把对应的文件删除,如果存储在共享空间之中即使表删除了空间也不会释放。
3:表的结构信息存在哪里?
首先,表结构定义占有的存储空间比较小,在MySQL8.0之前,表结构的定义信息存在以.frm为后缀的文件里,在MySQL8.0之后,则允许把表结构的定义信息存在系统数据表之中。
系统数据表,主要用于存储MySQL的系统数据,比如:数据字典、undo log(默认)等文件
4:如何才能删除表数据后,表文件大小就变小?
重建表,消除表因为进行大量的增删改操作而产生的空洞,使用如下命令:
5:空洞是啥?咋产生的?
空洞就是那些被标记可复用但是还没被使用的存储空间。
使用delete命令删除数据会产生空洞,标记为可复用
插入新的数据可能引起页分裂,也可能产生空洞
修改操作,有时是一种先删后插的动作也可能产生空洞
又刷新了认知,先给结论(之前不知从哪看的,以为count(主键id)性能最佳)
按照效率排序的话,count(字段)
count(*)这么慢,我该怎么办?
要么忍,要么自己动手记录,如果自己记录的话,老师建议使用数据库来弄,感觉使用数据库自己弄的思路可以建议MySQL实现一下?
count()的语义是啥?
首先,不同的存储引擎实现方式不同
MyISAM 引擎把一个表的总行数存在了磁盘上,因此执行 count() 的时候会直接返回这个数,效率很高;
而 InnoDB 引擎就麻烦了,它执行 count() 的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数。
以下针对innodb来说
count() 是一个聚合函数,对于返回的结果集,一行行地判断,如果 count 函数的参数不是 NULL,累计值就加 1,否则不加,最后返回累计值。
count(字段)怎么计数?
4-1:如果这个“字段”是定义为 not null 的话,一行行地从记录里面读出这个字段,判断不能为 null,按行累加;
4-2:如果这个“字段”定义允许为 null,那么执行的时候,判断到有可能是 null,还要把值取出来再判断一下,不是 null 才累加。
从引擎返回的字段会涉及到解析数据行,以及拷贝字段值的操作。
count(主键 id)怎么计数?
对于 count(主键 id) 来说,InnoDB 引擎会遍历整张表,把每一行的 id 值都取出来,返回给 server 层。server 层拿到 id 后,判断是不可能为空的,就按行累加。从引擎返回的 主键id 会涉及到解析数据行,以及拷贝字段值的操作。
count(1)怎么计数?
对于 count(1) 来说,InnoDB 引擎遍历整张表,但不取值。server 层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。
对于count()来说,并不会把全部字段取出来,而是专门做了优化,不取值。count() 肯定不是 null,按行累加。
8-1:server 层要什么就给什么;
8-2:InnoDB 只给必要的值;
8-3:现在的优化器只优化了 count(*) 的语义为“取行数”,其他“显而易见”的优化并没有做。
这几条原则对别的性能差别的分析也是OK的吧?
达到同样的目标,谁绕的弯越多做的事情越多就会越慢,性能自然不咋滴!不过知道每种达到目的的路径轨迹是一个难点,如果知道谁不喜欢走捷径呢?
https://time.geekbang.org/column/article/73161
1.MySQL会为每个线程分配一个内存(sort_buffer)用于排序该内存大小为sort_buffer_size
2.mysql会通过遍历索引将满足条件的数据读取到sort_buffer,并且按照排序字段进行快速排序
全字段排序
1.通过索引将所需的字段全部读取到sort_buffer中
2.按照排序字段进行排序
3.将结果集返回给客户端
缺点:
1.造成sort_buffer中存放不下很多数据,因为除了排序字段还存放其他字段,对sort_buffer的利用效率不高
2.当所需排序数据量很大时,会有很多的临时文件,排序性能也会很差
优点:MySQL认为内存足够大时会优先选择全字段排序,因为这种方式比rowid 排序避免了一次回表操作
rowid排序
1.通过控制排序的行数据的长度来让sort_buffer中尽可能多的存放数据,max_length_for_sort_data
2.只将需要排序的字段和主键读取到sort_buffer中,并按照排序字段进行排序
3.按照排序后的顺序,取id进行回表取出想要获取的数据
4.将结果集返回给客户端
优点:更好的利用内存的sort_buffer进行排序操作,尽量减少对磁盘的访问
缺点:回表的操作是随机IO,会造成大量的随机读,不一定就比全字段排序减少对磁盘的访问
3.按照排序的结果返回客户所取行数
如果你直接使用 order by rand(),这个语句需要 Using temporary 和 Using filesort,查询的执行代价往往是比较大的。所以,在设计的时候你要尽量避开这种写法。
今天的例子里面,我们不是仅仅在数据库内部解决问题,还会让应用代码配合拼接 SQL 语句。在实际应用的过程中,比较规范的用法就是:尽量将业务逻辑写在业务代码中,让数据库只做“读写数据”的事情。因此,这类方法的应用还是比较广泛的。
对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。
第二个例子是隐式类型转换,第三个例子是隐式字符编码转换,它们都跟第一个例子一样,因为要求在索引字段上做函数操作而导致了全索引扫描。
MySQL 的优化器确实有“偷懒”的嫌疑,即使简单地把 where id+1=1000 改写成 where id=1000-1 就能够用上索引快速查找,也不会主动做这个语句重写。
因此,每次你的业务代码升级时,把可能出现的、新的 SQL 语句 explain 一下,是一个很好的习惯。
课前思考
1:为啥只查一行的语句,也执行这么慢?
查的慢,基本上就是索引使用的问题,和查一行还是N行(N不是巨大),没有必然联系。查一行慢,猜测没有走索引查询,且数据量比较大。
课后思考
1:阅后发现自己的无知,只查询一行的语句,也比较慢,原因从大到小可分为三种情况?
第一MySQL数据库本身被堵住了,比如:系统或网络资源不够
第二SQL语句被堵住了,比如:表锁,行锁等,导致存储引擎不执行对应的SQL语句
第三确实是索引使用不当,没有走索引
第四是表中数据的特点导致的,走了索引,但回表次数庞大
感谢老师的分享,真是醍醐灌顶呀
幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。
“幻读”做一个说明
首先是语义上的,其次是数据一致性的问题。
为了解决幻读问题,InnoDB 只好引入新的锁,也就是间隙锁 (Gap Lock)。
比如行锁,分成读锁和写锁。下图就是这两种类型行锁的冲突关系。
也就是说,跟行锁有冲突关系的是“另外一个行锁”。
但是间隙锁不一样**,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作**。间隙锁之间都不存在冲突关系。
间隙锁和 next-key lock 的引入,帮我们解决了幻读的问题,但同时也带来了一些“困扰”。
间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。
总结的加锁规则里面,包含了两个“原则”、两个“优化”和一个“bug”。
**在删除数据的时候尽量加 limit。**这样不仅可以控制删除数据的条数,让操作更安全,还可以减小加锁的范围。
第一种方法:先处理掉那些占着连接但是不工作的线程。
第二种方法:减少连接过程的消耗。
慢查询性能问题
在 MySQL 中,会引发性能问题的慢查询,大体有以下三种可能:
导致慢查询的第一种可能是,索引没有设计好。
这种场景一般就是通过紧急创建索引来解决。MySQL 5.6 版本以后,创建索引都支持 Online DDL 了,对于那种高峰期数据库已经被这个语句打挂了的情况,最高效的做法就是直接执行 alter table 语句。
导致慢查询的第二种可能是,语句没写好。
MySQL 5.7 提供了 query_rewrite 功能,可以把输入的一种语句改写成另外一种模式。
导致慢查询的第三种可能,就是碰上了我们在第 10 篇文章《MySQL 为什么有时候会选错索引?》中提到的情况,MySQL 选错了索引。
这时候,应急方案就是给这个语句加上 force index。
同样地,使用查询重写功能,给原来的语句加上 force index,也可以解决这个问题。
QPS 突增问题
有时候由于业务突然出现高峰,或者应用程序 bug,导致某个语句的 QPS 突然暴涨,也可能导致 MySQL 压力过大,影响服务。
binlog 的写入机制
其实,binlog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 binlog 文件中。
write 和 fsync 的时机,是由参数 sync_binlog 控制的:
redo log 的写入机制
redo log 可能存在的三种状态
这三种状态分别是:
日志写到 redo log buffer 是很快的,wirte 到 page cache 也差不多,但是持久化到磁盘的速度就慢多了。
为了控制 redo log 的写入策略,InnoDB 提供了 innodb_flush_log_at_trx_commit 参数,它有三种可能取值:
两种场景会让一个没有提交的事务的 redo log 写入到磁盘中。
如果你的 MySQL 现在出现了性能瓶颈,而且瓶颈在 IO 上,可以通过哪些方法来提升性能呢?
1、简单主备,一主多备,主进行更新操作,将生成binlog文件发送给备,但是比较好奇一点的是所有备向主拿binlog文件的时候,主都是一个线程进行将binlog文件依次发送给备么?
两个库互为主备可以将一个负责数据的写入,生成binlog文件,另一个作为数据的同步,将其改变的binlog同步到自身,然后其他备再从其同步binlog,多master可以做到一台宕机,快速切换到另一台作为主,防止主库宕机对业务造成的影响,但是这样可能导致一定程度的同步延迟。
2、主备复制关系搭建完成,主有数据写入的时候,发送给备的应该不是整个binlog log文件吧,是每次写入的binlog event么?
3、在图 2 主备流程图对bg-thread->undolog(disk)->data(disk)不太理解,回滚段也是先记录到内存,再记录在磁盘么?undolog(disk)再到data(disk),看了下undo log的控制参数没有看到控制类似行为的,没想通?老师帮忙解答下哦
4、binlog的三种格式,statement,记录数据库原句,有可能导致,主备所选择的索引不一致,导致主备数据不一致。row,binlog log记录的是操作的字段值,根据binlog_row_image 的默认配置是 FULL包括操作行为的所有字段值,binlog_row_image 设置为 MINIMAL,则会记录必须的字段,一般设置为row,可以根据binlog文件做其他操作,比如在误删除一行数据时,可以做insert,恢复数据。
5、如果执行的是 update 语句的话,binlog 里面会记录修改前整行的数据和修改后的整行数据,在二级索引的普通索引,有个change buffer优化,防止频繁的将数据页读入进来,可以减少buffer pool的消耗,可以在读取数据时,再将其marge,或者后台线程marge,但是在binlog log设置row格式的,update时,需要记录更新前后的数据,那这样的话,chage buffer不是用不上了么?还是说设置成row格式的时候,change buffer会没生效?老师麻烦帮忙解答下哦,没想明白
作者回复: 2. 流式发送,一个事务提交就会发
\3. “回滚段也是先记录到内存,再记录在磁盘么?” 是的。 undolog(disk)不需要到data(disk),undo log的作用看一下08篇
\5. “update时,需要记录更新前后的数据,那这样的话,chage buffer不是用不上了么” — 不是的,binlog里面的内容用的是主键索引上的,主键索引确实用不上change buffer,但是普通索引可以
遇到过下面几种造成主从延迟的情况:
1.主库DML语句并发大,从库qps高
2.从库服务器配置差或者一台服务器上几台从库(资源竞争激烈,特别是io)
3.主库和从库的参数配置不一样
4.大事务(DDL,我觉得DDL也相当于一个大事务)
5.从库上在进行备份操作
6.表上无主键的情况(主库利用索引更改数据,备库回放只能用全表扫描,这种情况可以调整slave_rows_search_algorithms参数适当优化下)
7.设置的是延迟备库
8.备库空间不足的情况下
1、主备延迟,就是在同一个事务在备库执行完成的时间和主库执行完成的时间之间的差值,包括主库事务执行完成时间和将binlog发送给备库,备库事务的执行完成时间的差值。每个事务的seconds_behind_master延迟时间,每个事务的 binlog 里面都有一个时间字段,用于记录主库上的写入时间,备库取出当前正在执行的事务的时间字段的值,计算它与当前系统时的差值。
2、主备延迟的来源
①首先,有些部署条件下,备库所在机器的性能要比主库所在的机器性能差,原因多个备库部署在同一台机器上,大量的查询会导致io资源的竞争,解决办法是配置”双1“,redo log和binlog都只write fs page cache
②备库的压力大,产生的原因大量的查询操作在备库操作,耗费了大量的cpu,导致同步延迟,解决办法,使用一主多从,多个从减少备的查询压力
③大事务,因为如果一个大的事务的dml操作导致执行时间过长,将其事务binlog发送给备库,备库也需执行那么长时间,导致主备延迟,解决办法尽量减少大事务,比如delete操作,使用limit分批删除,可以防止大事务也可以减少锁的范围。
④大表的ddl,会导致主库将其ddl binlog发送给备库,备库解析中转日志,同步,后续的dml binlog发送过来,需等待ddl的mdl写锁释放,导致主备延迟。
3、可靠性优先策略,
①判断备库 B 现在的 seconds_behind_master如果小于某个值(比如 5 秒)继续下一步,否则持续重试这一步,②把主库 A 改成只读状态,即把 readonly 设置为 true,
③判断备库 B 的 seconds_behind_master的值,直到这个值变成 0 为止;
把备库 B 改成可读写也就是把 readonly 设置为 false; 把业务请求切换到备库,个人理解如果发送过来的binlog在中转日志中有多个事务,业务不可用的时间,就是多个事务被运用的总时间。如果非正常情况下,主库掉电,会导致出现的问题,如果备库和主库的延迟时间短,在中转日志运用完成,业务才能正常使用,如果在中转日志还未运用完成,切换为备库会导致之前完成的事务,”数据丢失“,但是在一些业务场景下不可接受。
4、**可用性策略,**出现的问题:在双m,且binlog_format=mixed,会导致主备数据不一致,使用使用 row 格式的 binlog 时,数据不一致的问题更容易发现,因为binlog row会记录字段的所有值。
备库一般会延迟分钟级别,比如主库压力比较大的时候,备库有可能会延迟小时级别,为此mysql官方提供了多种多线程复制策略
1、5.6基于库的多线程复制策略,使用hash数据库名作为key,value为多少个事务修改此数据库,使用hash来分配多线程,如果一个新事务加入进来,如果有冲突的hash,分配给此线程,如果没有冲突分配给空闲的线程,感觉实现的思路使用队列+线程池,如果线程池中没有空闲的线程,就在队列中增加事务,如果队列满,分发器阻塞,不解析binlog,分发器是生产者,线程池是消费者,基于库的多线程复制有如下优点①构造 hash 值的时候很快,只需要库名;线程的hash项也很少②binlog不需要强制指定row,statement也可以拿到库名。缺点:①如果只有一个库单线程复制,可以将其热点表分布到多个库中(不推荐使用),如果多个库的热点程度不同也会使其单线程复制。
2、基于表的多线程复制(非官方,老师实现),hash数据库名+表名作为key,value为多少个事务修改此数据表,同一个事务的多张表,在同一个线程进行处理,防止违反原子性,优点对同一个库多个热点表可以同时复制,多表负载效果很好,如果碰到热点表,比如所有的更新事务都会涉及到某一个表的时候,会使用单线程复制。
3、基于行的多线程复制,key必须是“库名 + 表名 + 唯一键的值“也需考虑唯一主键,防止唯一主键冲突(cpu的多线程调度,顺序不固定),value为修改前后key的次数,约束①表必须有主键②不能有外键③binlog格式row(表复制也一样)缺点:①大事务耗cpu②hash项多。优化可以设置阈值,如果事务修改的行大于特定值,使用单线程复制(老师自己实现)。mysql官网基于行的多线程复制,表示的是对于事务涉及更新的每一行,计算出每一行的 hash保存在writeset中,优点,①是有mysql主库写入binlog中,不需要解析 binlog 内容(event 里的行数据),节省计算量②binlog格式没要求,可以使用statement③无需扫描整个事务的binlog省内存,mysql5.7.22的多线程复制实现方式。
4、mysql5.7的多线程复制实现方式,借助于处于redo prepare到commit状态下的事务可以并行,因为执行器找引擎拿数据时,事务如果锁冲突会阻塞,无法到写redo log这一步,可以使用binlog故意延迟fsync,防止频繁写磁盘操作,不会丢失数据(redo prepar+完整的binlog事务才能提交,否则回滚),使其在备库多线程复制,主备延迟低,,但是这样有一点不好,语句的响应时间变长,感觉mysql官网故意延迟redo的fsync,在binlog write的时候(因为事务的binlog要写完整,时间较长),使其能批量提交,减少iops,感觉很巧妙
基于位点的主备切换
一种取同步位点的方法是这样的:
从库 B 的同步线程就会报告 Duplicate entry ‘id_of_R’ for key ‘PRIMARY’ 错误,提示出现了主键冲突,然后停止同步。
通常情况下,我们在切换任务的时候,要先主动跳过这些错误,有两种常用的方法。一种做法是,主动跳过一个事务。跳过命令的写法是:
另外一种方式是,通过设置 slave_skip_errors 参数,直接设置跳过指定的错误。
GTID
GTID 的全称是 Global Transaction Identifier,也就是全局事务 ID,是一个事务在提交的时候生成的,是这个事务的唯一标识。它由两部分组成,格式是:
GTID=server_uuid:gno
基于 GTID 的主备切换
GTID 和在线 DDL
但是,不论使用哪种架构,你都会碰到我们今天要讨论的问题:由于主从可能存在延迟,客户端执行完一个更新事务后马上发起查询,如果查询选择的是从库的话,就有可能读到刚刚的事务更新之前的状态。
这种“在从库上会读到系统的一个过期状态”的现象,在这篇文章里,我们暂且称之为“过期读”。
这里,我先把文章中涉及到的处理过期读的方案汇总在这里,以帮助你更好地理解和掌握全文的知识脉络。这些方案包括:
强制走主库方案
强制走主库方案其实就是,将查询请求做分类。通常情况下,我们可以将查询请求分为这么两类:
Sleep 方案
主库更新后,读从库之前先 sleep 一下。具体的方案就是,类似于执行一条 select sleep(1) 命令。
判断主备无延迟方案
所以第一种确保主备无延迟的方法是,每次从库执行查询请求前,先判断 seconds_behind_master 是否已经等于 0。如果还不等于 0 ,那就必须等到这个参数变为 0 才能执行查询请求。
第二种方法,对比位点确保主备无延迟:
第三种方法,对比 GTID 集合确保主备无延迟:
配合 semi-sync
要解决这个问题,就要引入半同步复制,也就是 semi-sync replication。
semi-sync 做了这样的设计:
等主库位点方案
GTID 方案
select wait_for_executed_gtid_set(gtid_set, 1);
等 GTID 的执行流程就变成了:
select 1 判断
实际上,select 1 成功返回,只能说明这个库的进程还在,并不能说明主库没问题。
并发连接和并发查询,并不是同一个概念。你在 show processlist 的结果里,看到的几千个连接,指的就是并发连接。而“当前正在执行”的语句,才是我们所说的并发查询。
并发连接数达到几千个影响并不大,就是多占一些内存而已。我们应该关注的是并发查询,因为并发查询太高才是 CPU 杀手。这也是为什么我们需要设置 innodb_thread_concurrency 参数的原因。
查表判断
mysql> select * from mysql.health_check;
使用这个方法,我们可以检测出由于并发线程过多导致的数据库不可用的情况。但是,我们马上还会碰到下一个问题,即:空间满了以后,这种方法又会变得不好使。
更新判断
既然要更新,就要放个有意义的字段,常见做法是放一个 timestamp 字段,用来表示最后一次执行检测的时间。这条更新语句类似于:
mysql> update mysql.health_check set t_modified=now();
你一定会疑惑,更新语句,如果失败或者超时,就可以发起主备切换了,为什么还会有判定慢的问题呢?
其实,这里涉及到的是服务器 IO 资源分配的问题。
内部统计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rt60jyIO-1615554620612)(MySQL 实战45讲–笔记.assets/image-20210215094032796.png)]
不等号条件里的等值查询
等值查询的过程
怎么看死锁?
怎么看锁等待?
update 的例子
总结下今天的知识点:
我觉得DBA的最核心的工作就是保证数据的完整性
今天老师也讲到了先要做好预防,预防的话大概是通过这几个点:
1.权限控制与分配(数据库和服务器权限)
2.制作操作规范
3.定期给开发进行培训
4.搭建延迟备库
5.做好sql审计,只要是对线上数据有更改操作的语句(DML和DDL)都需要进行审核
6.做好备份。备份的话又分为两个点.
(1)如果数据量比较大,用物理备份xtrabackup。定期对数据库进行全量备份,也可以做增量备份。
(2)如果数据量较少,用mysqldump或者mysqldumper。再利用binlog来恢复或者搭建主从的方式来恢复数据。
定期备份binlog文件也是很有必要的
还需要定期检查备份文件是否可用,如果真的发生了误操作,需要恢复数据的时候,发生备份文件不可用,那就更悲剧了
如果发生了数据删除的操作,又可以从以下几个点来恢复:
1.DML误操作语句造成数据不完整或者丢失。可以通过flashback,不过我们目前用的是美团的myflash,也是一个不错的工具,本质都差不多.都是先解析binlog event,然后在进行反转。把delete反转为insert,insert反转为delete,update前后image对调。所以必须设置binlog_format=row 和 binlog_row_image=full.
切记恢复数据的时候,应该先恢复到临时的实例,然后在恢复回主库上。
2.DDL语句误操作(truncate和drop),由于DDL语句不管binlog_format是row还是statement.在binlog里都只记录语句,不记录image所以恢复起来相对要麻烦得多。只能通过全量备份+应用binlog的方式来恢复数据。一旦数据量比较大,那么恢复时间就特别长,
对业务是个考验。所以就涉及到老师在第二讲提到的问题了,全量备份的周期怎么去选择
收到kill以后,线程做什么?
实现上,当用户执行 kill query thread_id_B 时,MySQL 里处理 kill 命令的线程做了两件事:
这时候,从 show processlist 结果上看也是 Command=Killed,需要等到终止逻辑完成,语句才算真正完成。这类情况,比较常见的场景有以下几种:
另外两个关于客户端的误解
第一个误解是:如果库里面的表特别多,连接就会很慢。
我们感知到的连接过程慢,其实并不是连接慢,也不是服务端慢,而是客户端慢。
–quick 是一个更容易引起误会的参数,也是关于客户端常见的一个误解。
MySQL 客户端发送请求后,接收服务端返回结果的方式有两种:
那你会说,既然这样,为什么要给这个参数取名叫作 quick 呢?这是因为使用这个参数可以达到以下三点效果
全表扫描对 server 层的影响
实际上,服务端并不需要保存一个完整的结果集。取数据和发数据的流程是这样的:
MySQL 是“边读边发的”,这个概念很重要。这就意味着,如果客户端接收得慢,会导致 MySQL 服务端由于结果发不出去,这个事务的执行时间变长。
对于正常的线上业务来说,如果一个查询的返回结果不会很多的话,我都建议你使用 mysql_store_result 这个接口,直接把查询结果保存到本地内存。
实际上,一个查询语句的状态变化是这样的(注意:这里,我略去了其他无关的状态):
全表扫描对 InnoDB 的影响
InnoDB 管理 Buffer Pool 的 LRU 算法,是用链表来实现的。
Index Nested-Loop Join
怎么选择驱动表?
在这个 join 语句执行过程中,驱动表是走全表扫描,而被驱动表是走树搜索。
到这里小结一下,通过上面的分析我们得到了两个结论:
Simple Nested-Loop Join
Block Nested-Loop Join
这时候,被驱动表上没有可用的索引,算法的流程是这样的:
Multi-Range Read 优化
因为大多数的数据都是按照主键递增顺序插入得到的,所以我们可以认为,如果按照主键的递增顺序查询的话,对磁盘的读比较接近顺序读,能够提升读性能。
MRR 能够提升性能的核心在于,这条查询语句在索引 a 上做的是一个范围查询(也就是说,这是一个多值查询),可以得到足够多的主键 id。这样通过排序以后,再去主键索引查数据,才能体现出“顺序性”的优势。
Batched Key Access
理解了 MRR 性能提升的原理,我们就能理解 MySQL 在 5.6 版本后开始引入的 Batched Key Access(BKA) 算法了。这个 BKA 算法,其实就是对 NLJ 算法的优化。
BNL 算法的性能问题
大表 join 操作虽然对 IO 有影响,但是在语句执行结束后,对 IO 的影响也就结束了。但是,对 Buffer Pool 的影响就是持续性的,需要依靠后续的查询请求慢慢恢复内存命中率。
BNL 转 BKA
一些情况下,我们可以直接在被驱动表上建索引,这时就可以直接转成 BKA 算法了。
扩展 -hash join
这,也正是 MySQL 的优化器和执行器一直被诟病的一个原因:不支持哈希 join。并且,MySQL 官方的 roadmap,也是迟迟没有把这个优化排上议程。实际上,这个优化思路,我们可以自己实现在业务端。
临时表的特性
临时表的应用
由于不用担心线程之间的重名冲突,临时表经常会被用在复杂查询的优化过程中。其中,分库分表系统的跨库查询就是一个典型的使用场景。
为什么临时表可以重名?
MySQL 维护数据表,除了物理上要有文件外,内存里面也有一套机制区别不同的表,每个表都对应一个 table_def_key。
也就是说,session A 和 sessionB 创建的两个临时表 t1,它们的 table_def_key 不同,磁盘文件名也不同,因此可以并存。
临时表和主备复制
在 binlog_format='row’的时候,临时表的操作不记录到 binlog 中,也省去了不少麻烦,这也可以成为你选择 binlog_format 时的一个考虑因素。需要注意的是,我们上面说到的这种临时表,是用户自己创建的 ,也
通过今天这篇文章,我重点和你讲了 group by 的几种实现算法,从中可以总结一些使用的指导原则:
(内存表不支持事务)
内存表的数据组织结构
InnoDB 引擎把数据放在主键索引上,其他索引上保存的是主键 id。这种方式,我们称之为索引组织表(Index Organizied Table)。
而 Memory 引擎采用的是把数据单独存放,索引上保存数据位置的数据组织形式,我们称之为堆组织表(Heap Organizied Table)。
从中我们可以看出,这两个引擎的一些典型不同:
hash 索引和 B-Tree 索引
内存表也是支 B-Tree 索引的。在 id 列上创建一个 B-Tree 索引
其实,一般在我们的印象中,内存表的优势是速度快,其中的一个原因就是 Memory 引擎支持 hash 索引。
但是,接下来我要跟你说明,为什么我不建议你在生产环境上使用内存表。这里的原因主要包括两个方面:
内存表的锁
我们先来说说内存表的锁粒度问题。
内存表不支持行锁,只支持表锁。因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作。
数据持久性问题
数据放在内存中,是内存表的优势,但也是一个劣势。因为,数据库重启的时候,所有的内存表都会被清空。
我们先看看 M-S 架构下,使用内存表存在的问题。
建议你把普通内存表都用 InnoDB 表来代替。但是,有一个场景却是例外的。
这个场景就是,我们在第 35 和 36 篇说到的用户临时表。在数据量可控,不会耗费过多内存的情况下,你可以考虑使用内存表。
内存临时表刚好可以无视内存表的两个不足,主要是下面的三个原因:
最喜欢这样的文章,以为比较简单和熟悉,也能打开一扇窗,让人看到一个不同的世界,并且无比丰富多彩。
在什么场景下自增主键可能不连续?
1:唯一键冲突
2:事务回滚
3:自增主键的批量申请
深层次原因是,不判断自增主键是否已存在和减少加锁的时间范围和粒度->为了更高的性能->自增主键不能回退->自增主键不连续
自增主键是怎么做的唯一性的?
自增值加1,自增锁控制并发
自增主键的生成性能如何?
这个需要测试一下,数据库的自增主键也用做生成唯一数字,作为其他单号,比如:并发量小的订单号,性能可能一般。
自增主键有最大值嘛?如果有,到了咋弄?
最大值应该有,因为数字总有个范围,到了当做字符串的一部分,然后再自增拼接上另一部分,貌似也可以。
自增主键的作用?保存机制?修改机制?
作用:让主键索引尽量地保持递增顺序插入,避免页分裂,使索引更紧凑。
保存机制:不同的存储引擎不一样。
MyISAM 引擎的自增值保存在数据文件中。
InnoDB 引擎的自增值,先是保存在了内存里,到了 MySQL 8.0 版本后,才有了“自增值持久化”的能力,放在了redolog里。
修改机制:
在 MySQL 里面,如果字段 id 被定义为 AUTO_INCREMENT,在插入一行数据的时候,自增值的行为如下:
1:如果插入数据时 id 字段指定为 0、null 或未指定值,那么就把这个表当前的 AUTO_INCREMENT 值填到自增字段;
2:如果插入数据时 id 字段指定了具体的值,就直接使用语句里指定的值。
根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是 X,当前的自增值是 Y。
1:如果 X
insert … select 语句
insert 循环写入
当然了,执行 insert … select 的时候,对目标表也不是锁全表,而是只锁住需要访问的资源。
insert 唯一键冲突
insert into … on duplicate key update
insert into … on duplicate key update 这个语义的逻辑是,插入一行数据,如果碰到唯一键约束,就执行后面的更新语句。
小结
今天这篇文章,我和你介绍了几种特殊情况下的 insert 语句。
insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。
而如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。
insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。
今天这篇文章,我和你介绍了三种将一个表的数据导入到另外一个表中的方法。
我们来对比一下这三种方法的优缺点。
表当前的 AUTO_INCREMENT 值填到自增字段;
2:如果插入数据时 id 字段指定了具体的值,就直接使用语句里指定的值。
根据要插入的值和当前自增值的大小关系,自增值的变更结果也会有所不同。假设,某次要插入的值是 X,当前的自增值是 Y。
1:如果 X
insert … select 语句
insert 循环写入
当然了,执行 insert … select 的时候,对目标表也不是锁全表,而是只锁住需要访问的资源。
insert 唯一键冲突
insert into … on duplicate key update
insert into … on duplicate key update 这个语义的逻辑是,插入一行数据,如果碰到唯一键约束,就执行后面的更新语句。
小结
今天这篇文章,我和你介绍了几种特殊情况下的 insert 语句。
insert … select 是很常见的在两个表之间拷贝数据的方法。你需要注意,在可重复读隔离级别下,这个语句会给 select 的表里扫描到的记录和间隙加读锁。
而如果 insert 和 select 的对象是同一个表,则有可能会造成循环写入。这种情况下,我们需要引入用户临时表来做优化。
insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长。
今天这篇文章,我和你介绍了三种将一个表的数据导入到另外一个表中的方法。
我们来对比一下这三种方法的优缺点。