大字段如何对查询产生影响

2021-10-10 16:10:31 954

一些应用, 在表结构的设计上使用了text或者blob的字段;其中一个应用,对blob/text字段的依赖非常的严重,查询和更新的频率也是非常的高,单表的存储空间已经达到了近100G,这个时候,应用其实已经被数据库绑死了,任何应用或者查询逻辑的变更几乎成为不可能;

为了清楚大字段对性能的影响,我们必须要知道innodb存储引擎在底层对行的处理方式:

在5.1中,innodb存储引擎的默认的行格式为compact(redundant为兼容以前的版本),对于blob,text,varchar(8099)这样的大字段,innodb只会存放前768字节在数据页中,而剩余的数据则会存储在溢出段中(发生溢出情况的时候适用);

53fad34f-9f64-36d8-a3c1-920e849bedb8.jpg

innodb的块大小默认为16kb,由于innodb存储引擎表为索引组织表,树底层的叶子节点为一双向链表,因此每个页中至少应该有两行记录,这就决定了innodb在存储一行数据的时候不能够超过8k(8098字节);

使用了blob数据类型,是不是一定就会存放在溢出段中?通常我们认为blob,clob这类的大对象的存储会把数据存放在数据页之外,其实不然,关键点还是要看一个page中到底能否存放两行数据,blob可以完全存放在数据页中(单行长度没有超过8098字节),而varchar类型的也有可能存放在溢出页中(单行长度超过8098字节,前768字节存放在数据页中); 5.1中的innodb_plugin引入了新的文件格式:barracuda(将compact和redundant合称为antelope),该文件格式拥有新的两种行格式:compressed和dynamic,两种格式对blob字段采用完全溢出的方式,数据页中只存放20字节,其余的都存放在溢出段中:

bf6ee803-69f3-3c1e-9b9d-d4c72f42fc6c.jpg

mysql在操作数据的时候,以page为单位,不管是更新,插入,删除一行数据,都需要将那行数据所在的page读到内存中,然后在进行操作,这样就存在一个命中率的问题,如果一个page中能够相对的存放足够多的行,那么命中率就会相对高一些,性能就会有提升; 有了上面的知识点,我们一起看看该应用的特点,表结构:

CREATE TABLE `xx_msg` (
  `col_user` VARCHAR(64)  NOT NULL,
  `col_smallint` SMALLINT(6) NOT NULL,
  `col_lob` longblob,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xxx`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk

col_lob为blob字段,用于存放该用户的所有的消息,其平均长度在2.4kb左右,该表中其他剩余的字段则是非常的小,大致在60字节左右

SELECT avg(LENGTH(col_clob)) FROM (SELECT * fromxxx_msg LIMIT 30000)a;
|         2473.8472 |

该表的应用场景包括:

  1. select col_user ,col_smallint,DATE_FORMAT(gmt_modified,’%Y-%m-%d’) from xx_msg;
  2. update xx_msg set gmt_modified=’2012-03-31 23:16:30′,col_smallint=1,col_lob=’xxx’ where col_user=’xxx’;
  3. select col_smallint from xx_msg where user=’xxx’;

可以看到由于单行的平均长度(2.5k)还远小于一个innodb page的size(16k)(当然也有存在超过8k的行),也就是知识点三中提到的,blob并不会存放到溢出段中,而是存放到数据段中去,innodb能够将一行的所有列(包括longlob)存储在数据页中:

5813e13d-f52e-36c5-8c6b-dc39ffc4e393.jpg 在知识点五中,mysql的io以page为单位,因此不必要的数据(大字段)也会随着需要操作的数据一同被读取到内存中来,这样带来的问题由于大字段会占用较大的内存(相比其他小字段),使得内存利用率较差,造成更多的随机读取。

从上面的分析来看,我们已经看到性能的瓶颈在于由于大字段存放在数据页中,造成了内存利用较差,带来过多的随机读,那怎么来优化掉这个大字段的影响:

一. 压缩:

在知识点四中,innodb提供了barracuda文件格式,将大字段完全存放在溢出段中,数据段中只存放20个字节,这样就大大的减小了数据页的空间占用,使得一个数据页能够存放更多的数据行,也就提高了内存的命中率(对于本实例,大多数行的长度并没有超过8k,所以优化的幅度有限);如果对溢出段的数据进行压缩,那么在空间使用上也会大大的降低,具体的的压缩比率可以设置key_blok_size来实现。

二. 拆分:

将主表拆分为一对一的两个关联表:

CREATE TABLE `xx_msg` (
  `col_user` VARCHAR(64)  NOT NULL,
  `col_smallint` SMALLINT(6) NOT NULL,
  `gmt_create` datetime DEFAULT NULL,
  `gmt_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`xxx`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk;
 
CREATE TABLE `xx_msg_lob` (
  `col_user` VARCHAR(64)  NOT NULL,
  `col_lob` longblob,
   PRIMARY KEY (`xxx`)
) ENGINE=InnoDB DEFAULT CHARSET=gbk

xx_msg表由于将大字段单独放到另外一张表后,单行长度变的非常的小,page的行密度相比原来的表大很多,这样就能够缓存足够多的行,表上的多个select由于buffer pool的高命中率而受益;应用程序需要额外维护的是一张大字段的子表;

三.覆盖索引:

在上面的两个查询当中,都是查询表中的小字段,由于老的方案需要全表或者根据主键来定位表中的数据,但是还是以page为单位进行操作,blob字段存在还是会导致buffer pool命中率的下降,如果通过覆盖索引来优化上面的两个查询,索引和原表结构分开,从访问密度较小的数据页改为访问密度很大的索引页,随机io转换为顺序io,同时内存命中率大大提升;额外的开销为数据库多维护一个索引的代价;

alter table xx_msg add index ind_msg(col_user ,col_smallint,gmt_modified);

对于查询一,原来的执行计划为走全表扫描,现在通过全索引扫描来完成查询;

对于查询二,原来的执行计划为走主键PK来定位数据,现在该走覆盖索引ind_msg完成查询;

注意上面的两个查询为了稳固执行计划,需要在sql执行中加入hint提示符来强制sql通过索引来完成查询;

总结:上面三种思路来优化大字段,其核心思想还是让单个page能够存放足够多的行,不断的提示内存的命中率,尽管方法不同,但条条大路通罗马,从数据库底层存储的原理出发,能够更深刻的优化数据库,扬长避短,达到意想不到的效果。

大表的另一种优化思路

最近在跟进服务迁移到华为云的工作, 发现mysql性能下降非常明显例:某大表SELECT COUNT(1)原先只要60s, 而华为云需要112s之多, 从纸面配置看, 两者没有什么明显区别.但后续在跟华为云技术多次沟通, 多次调整后, 确定了性能下降的原因有三个方面物理配置参数设置mysql版本相关
2024-01-02

OLAP / OLTP

数据库系统可以在广义上分为联机事务处理(Online Transaction Process,OLTP)联机分析处理(Online Analyze Process,OLAP)两种面向不同领域的数据库,OLAP数据库也被称为数据仓库。从产品上看,有专门面向OLTP的数据库,例如MySQL、Postgr
2023-10-15

MySQL Binlog/Redolog和CrashSafe机制

redo logredo log是MySQL InnoDB的日志, 是物理日志, 记录的是"在某个数据页上做了什么修改"提一下MySQL里经常说到的WAL技术, WAL的全称是Write Ahead Logging, 它的关键点就是先写日志, 再写磁盘. 日志是顺序写的, 磁盘是随机写. 顺序写速度
2022-12-12

使用 SOAR 优化 SQL

介绍soar是由小米开源的SQL优化器和重写器项目地址 https://github.com/XiaoMi/soar使用基于 ubuntu20.04 lts按文档安装后https://github.com/XiaoMi/soar/blob/master/doc/install.md现有 catego
2022-08-12

MySQL主从复制搭建

masterdocker run -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 --network rootnet --ip 172.18.0.10 -v /usr/soft/mysql-master/data:/var/lib/mysql:rw -v /u
2022-05-19

MySQL InnoDB 加锁机制

MySQL 版本: 8.0.25隔离级别: 可重复读InnoDB有两种不同的SELECT,即普通SELECT 和 锁定读SELECT. 锁定读SELECT 又有两种,即SELECT ... FOR SHARE 和 SELECT ... FOR UPDATE; 锁定读SELECT 之外的则是 普通SE
2022-03-26

MySQL InnoDB MVCC机制

对于普通select来说, InnoDB使用MVCC保证了事务隔离. 同一事务的两次相同查询语句都是同样结果, 其他事务修改记录不影响当前事务, 特殊情况是会看到同一事务中先前语句所做的更新, 所以对于普通select(快照读)来说, MVCC是解决了脏读/不可重复读/幻行的; 而对于当前读(锁定读
2022-02-23

MySQL WITH AS 语法

如果一整句查询中多个子查询都需要使用同一个子查询的结果,那么就可以用with as,将共用的子查询提取出来,加个别名。后面查询语句可以直接用,对于大量复杂的SQL语句起到了很好的优化作用特别对于UNION ALL比较有用. 因为UNION ALL的每个部分可能相同,但是如果每个部分都去执行一遍的话,
2022-02-20

MySQL优化-表结构设计

首先明确一个, 减少占用的存储空间, 可以减少操作时占用的内存, 可以提高CPU处理效率字符串的ip地址可以转换为整数类型存储, mysql提供INET_ATON()和INET_NTOA()进行转换尽量避免字段允许为NULL, 字段为NULL会占用额外空间整数类型可以选择置为无符号, 同样的存储空间
2021-11-14
大字段如何对查询产生影响

大字段如何对查询产生影响

一些应用, 在表结构的设计上使用了text或者blob的字段;其中一个应用,对blob/text字段的依赖非常的严重,查询和更新的频率也是非常的高,单表的存储空间已经达到了近100G,这个时候,应用其实已经被数据库绑死了,任何应用或者查询逻辑的变更几乎成为不可能;为了清楚大字段对性能的影响,我们必须
2021-10-10

常见的大表查询优化

测试表user, user_detail各100w数据下面是一个常见的连表查询分页sqlSELECT * FROM user u LEFT JOIN user_detail ud ON u.id = ud.user_id LIMIT 800000, 10 执行时间3.323s优化下可以写成这样SEL
2021-05-10

各平台时间格式

javayyyy-MM-dd HH:mm:ss2021-01-18 13:05:25mysqlDATE_FORMAT(time,'%Y-%m-%d %H:%i:%s')2021-01-18 13:05:25sql serverSELECT CONVERT(varchar(100), GETDATE(
2021-01-18

Mysql 通过binlog日志恢复数据

https://www.cnblogs.com/YCcc/p/10825870.html
2021-01-13

解决VM虚拟机启动后假死

一、开机后黑屏假死管理员cmd 输入netsh winsock reset,回车,重启系统二、启动后假死关闭本机防火墙
2020-03-27

如何快速上手一个项目

不知道你有没有经历过一个五年或者更长工作年限的开发人员半路加入团队的情况,可能第一两个星期他会问一些业务或者技术问题,不过一两个月他就可能在指导那些初级开发人员了。什么原因呢?因为他已经从过往经验里面总结出来一些套路了。那么套路是什么呢?绝大部分业务系统,不管他后端是oracle、mysql、nos
2020-03-28

每日邮件 推送天气、鸡汤、博客

功能邮件提醒每日天气每日鸡汤每日博客邮件格式你好,xxx,今天是2019年7月21日,农历六月十五 【每日天气】 城市[北京] 天气[晴]\n温度[31℃ ~ 24℃] 白天风力[东南风<3级] 夜间风力[东北风<3级] 紫外线指数[最弱--辐射弱,涂擦SPF8-12防晒护肤品]\n穿衣指数[短袖
2020-03-28

SpringDataRedis 常用操作

//向redis里存入数据和设置缓存时间 stringRedisTemplate.opsForValue().set("test", "100",60*10,TimeUnit.SECONDS); //根据key获取缓存中的val stringRedisTemplate.opsForValue().
2020-03-28

js向json中添加属性

object['attr'] = {};
2020-03-28

如何在Java中替换多个if语句

1. 概述选择结构是任何编程语言的重要组成部分。但是我们编写了大量嵌套的if语句,这使得我们的代码更加复杂和难以维护。 在本教程中,我们将介绍替换嵌套if语句的各种方法。 让我们探索如何简化代码的不同选项。2. 案例研究我们经常遇到涉及很多条件的业务逻辑,并且每个都需要不同的处理。为了演示,我们以C
2020-03-28

SpringBoot Undertow http跳转https

package com.mtons.mblog.config; import io.undertow.Undertow; import io.undertow.UndertowOptions; import io.undertow.servlet.api.SecurityConstraint; i
2020-03-28

Java中的Date和时区转换

1.Date中保存的是什么在java中,只要我们执行 Date date = new Date(); 就可以得到当前时间。如:Date date = new Date(); System.out.println(date); 输出结果是: Thu Aug 24 10:15:29 CST 2017 也
2020-03-28
解决SpringBoot+JPA中使用set方法时自动更新数据库问题

解决SpringBoot+JPA中使用set方法时自动更新数据库问题

首先引入EntityManager:然后用它来强转获得HibernateEntityManager,然后调用获得Session,然后在set完之后用Session的.evict()方法清掉该对象缓存(并非所有对象缓存),如此就ok了...好吧,已经java11不建议用了,不过还能用,先用着吧!更新了
2020-04-04