mysql
mysql的架构
server + 存储引擎
server: 连接器: 管理客户端的连接及权限管理 查询缓存: mysql8取消了这个模块 分析器: 语法、词法分析 优化器: 选择索引 执行器: 通过存储引擎的api查询数据
当时使用查询缓存,每次更新表,都会导致对应表的查询缓存失效
所在的数据页已经满了,根据B+树的算法,这时候需要申请一个新的数据页,然后挪动部分数据过去。这个过程称为页分裂。
自增主键的插入数据模式,正符合了我们前面提到的递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。
B+树: N叉树
以InnoDB的一个整数字段索引为例,这个N差不多是1200。这棵树高是4的时候,就可以存1200的3次方个值,这已经17亿了。考虑到树根的数据块总是在内存中的, 一个10亿行的表上一个整数字段的索引,查找一个值最多只需要访问3次磁盘。其实,树的第二层也有很大概率在内存中,那么访问磁盘的平均次数就更少了。
聚簇索引,回表、 覆盖索引、左前缀匹配、索引下推优化
索引下推: 5.6后引入,可以在索引遍历的过程中,对索引包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表的次数。
DDL: data definition language 数据模型语言
DML: data manipulation language 数据操作语言
全局锁,表锁,行锁 全局锁 Flush tables with read lock
表级锁分为:
1. 表锁 lock tables xxxx read/write
2. 元数据锁 (metadata lock)不需要显式声明,在访问一个表的时候会被自动加上
innodb存储引擎支持行锁
两阶段锁协议: 在innodb事务中,行锁是在需要的时候才加上的,但是并不是不需要了会立即释放,而是要等待事务结束才会释放。
死锁,死锁检测
死锁处理:
1. 锁超时时间 容易误判 innodb_lock_wait_timeout
2. 死锁检测,会降低并发度 innodb_deadlock_detect =on默认值就是on
优化
2.1 利用数据库中间件或修改Mysql源码,控制并发,相同的行更新,在进入引擎前排队
2.2 把要修改的一行记录, 差分多行增加并发能力,如账户余额有多条记录相加得到
如果事务中需要锁多个行,要把最可能造成锁冲突,最可能影响并发度的锁的申请实际进来往后放。
innodb的行数据,有多个版本,每个数据版本都有自己的row_trx_id
可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。
对于可重复读:查询只承认在事务启动前就已经提交完成的数据
对于读提交: 查询只承认在语句启动前就已经提交完成的数据。
innodb中每个事务都有一个transaction_id,它在事务开始的时候申请,严格按照顺序递增。
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读 current read”, 除了update,如果select加锁那么也是当前读
select k from t where id=1 lock in share mode; 读锁(s锁, 共享锁)
select k from t where id=1 for update; 写锁(X锁, 排它锁)
当读取一条记录时,并不是将这个记录本身从磁盘读出来,,而是以页为单位,将其整体读取内存中。在innodb中,每页默认16KB
唯一索引和普通索引 唯一索引和普通索引的查询过程性能区别微乎其微
普通索引使用 change buffer提升写的性能。
写多读少的场景使用change buffer的效果比较好: 如账单、日志
将change buffer中的操作应用到原数据页,得到最新结果的过程称为merge。除了访问这个数据页会触发merge外,系统有后台线程会定期merge。在数据库正常关闭(shutdown)的过程中,也会执行merge操作。
redo log 主要节省的是随机写磁盘的IO消耗(转成顺序写),而change buffer主要节省的则是随机读磁盘的IO消耗。
执行SQL会通过优化器选择索引, 扫描行数是影响执行代价的因素之一,这里优化器的扫描行数不是实际存储引擎的行数,是统计信息(采样统计)来估算记录数
采样统计: Innodb默认会选择N个数据页,统计这些页上的不同值,得到一个平均值,然后乘索引的页面数,就得到了这个索引的基数。
analyze table tableName 可以用来重新统计索引信息。修正不对的统计信息
强制使用某个索引: select * from tablename force index(indexName) 避免优化器选择错误的索引
对字符字段可以使用前缀索引,定义索引仅是字段的几个字节,如email字段,创建索引 add index index2(email(6))。使用前缀索引就会 使用不到覆盖索引对查询的性能优化了。
我们在创建索引时关注的是区分度,区分度越高越好。 因为区分度越高意味着重复的键值越少,越利于B+树查询。
redo log 内存页---> 磁盘数据
当内存数据页和磁盘数据页内容不一致的时候,我们称这个内存也为"脏页"。内存数据写入磁盘后,内存和磁盘的数据页的内容就保持一致了,称为“干净页”。
平时很快的SQL操作, 其实就是在写内存和日志,SQL偶尔抖一下,可能就是因为在刷(flush)"脏页"。
innodb用缓冲池(buffer poll)管理内存,缓存池中的内存页有三种状态 1.未使用 2.使用了并且是干净页 3.使用了是脏页
MySQL“抖”了一下要尽量避免这种情况,你就要合理地设置innodb_io_capacity的值,并且平时要多关注脏页比例,不要让它经常接近75%。
mysql默认一个表的数据数单独存储在一个文件中,如果只是delete一些行,表的大小时不会变化的。drop table是直接删除这个文件。
被删除只是数据也被标记,你可以执行alter table A engine=InnoDB(5.5之前这个是阻塞的,之后是online的)来重建表。是被标记的空间清理掉。 过程 1. 创建临时文件,复制老的文件的数据 2. 此时的增删改记录到日志文件(row log)中 3. 复制完成,日志文件的数据写入临时文件 4.删除老的文件,用临时文件代理老的文件 注意:如果表的大小事1TB,磁盘是1.2TB是没法重建表的。
count(1),count(*),count(主键)都表示返回满足条件的结果集的总行数了;而count(字段),则表示返回满足条件的数据行里,参数“字段”部位null的总个数。
优化器只优化了count(*)的语义为“取行数”,其他的显而易见没有优化
至于分析性能差别的时候,你可以记住这么几个原则:
1.server层要什么就给什么;
2.InnoDB只给必要的值;
3.现在的优化器只优化了count(*)的语义为“取行数”,其他“显而易见”的优化并没有做。
对于count(主键id)来说,InnoDB引擎会遍历整张表,把每一行的id值都取出来,返回给server层。server层拿到id后,判断是不可能为空的,就按行累加。 对于count(1)来说,InnoDB引擎遍历整张表,但不取值。server层对于返回的每一行,放一个数字“1”进去,判断是不可能为空的,按行累加。 对于count(字段)来说:如果这个“字段”是定义为not null的话,一行行地从记录里面读出这个字段,判断不能为null,按行累加;如果这个“字段”定义允许为null,那么执行的时候,判断到有可能是null,还要把值取出来再判断一下,不是null才累加。 但是count()是例外,并不会把全部字段取出来,而是专门做了优化,不取值。count()肯定不是null,按行累加。
count(*) ~= count(1) > count(主键) > count(字段)