
Mysql中的数据是通过一定的方式存储在文件或者内存中的,任何方式都有不同的存储、查找和更新机制,这意味着选择不同的方式对于数据的存取有效率的差距。 这种不同的存储方式在 MySQL中被称作存储引擎。
存储引擎是Mysql数据库系统的底层组件,数据库管理系统通过这些组件来进行创建、查询、更新和删除数据,它操作的对象是表。存储引擎类型在肉眼上体现为表的类型,Mysql的存储引擎类型可以通过运行命令' SHOW ENGINES; '获得,目前Mysql8支持的存储引擎有如下几种:
查看当前默认的存储引擎:
SHOW VARIABLES LIKE 'default_storage_engine%';
修改默认存储引擎(临时生效,重启后恢复InnoDB):
SET default_storage_engine=< 存储引擎名 >
修改单个数据表的存储引擎:
ALTER TABLE <表名> ENGINE=<存储引擎名>;
查看数据表的存储引擎:
SHOW CREATE TABLE tablename \G
修改表的默认存储引擎,要想永久生效,需要在配置文件my.cnf中 [mysqld] 后面加入:
default-storage-engine=存储引擎名称
如何根据业务场景来选择合适自己的存储引擎呢?这需要先了解几种存储引擎的特性,以下是几种存储引擎的特性表,可做参考:
| 特性 | MyISAM | InnoDB | MEMORY |
|---|---|---|---|
| 存储限制 | 有 | 支持 | 有 |
| 事务安全 | 不支持 | 支持 | 不支持 |
| 锁机制 | 表锁 | 行锁 | 表锁 |
| B树索引 | 支持 | 支持 | 支持 |
| 哈希索引 | 不支持 | 不支持 | 支持 |
| 全文索引 | 支持 | 不支持 | 不支持 |
| 集群索引 | 不支持 | 支持 | 不支持 |
| 数据缓存 | 支持 | 支持 | |
| 索引缓存 | 支持 | 支持 | 支持 |
| 数据可压缩 | 支持 | 不支持 | 不支持 |
| 空间使用 | 低 | 高 | N/A |
| 内存使用 | 低 | 高 | 中等 |
| 批量插入速度 | 高 | 低 | 高 |
| 支持外键 | 不支持 | 支持 | 不支持 |
InnoDB是最常用的存储引擎,我们的业务系统在使用Mysql作为数据库的时候,一般都选择InnoDB作为存储引擎,原因是该引擎支持行锁、索引、事务安装、主外键,而这些特性正是业务系统数据所需要的。
数据在InnoDB中将被划分为若干个页,页的大小一般为16KB,以页作为存取数据的单位,前一节提到InnoDB的数据是存到磁盘上的,而程序代码需要的数据是在内存中的,因此存取数据的操作实际上是磁盘和内存之间数据交换的过程。
以下是Mysql数据页的结构图,不同的部分代表着不同的功能,其中:

UserRecord这部分空间是可变的,当向数据库中插入记录时,数据会被存到这个位置,UserRecord空间被增加,同时FreeSpace容量会变小,直到空闲空间FreeSpace被用完,如果还有新的数据插入,则引擎会申请新的数据页再进行存储。

多条数据被存到UserRecord这个位置,那他们之间的物理位置有什么关系呢?这里牵涉到两种主要关系,一种是同一数据页之间的数据记录位置关系,另一种是不同数据页之间的记录的位置关系。要谈记录的位置关系,我们要了解每条记录的存储结构。
每条记录都包含我们存储的真实字段数据和记录额外的信息数据,一行记录可以以不同的行格式存在,目前有Compact、Redundant、Dynamic、Compressed等几种格式,以下是Compact行格式的图解:

其中,每个属性说明如下:
| 名称 | 大小(bit) | 描述 |
|---|---|---|
| 预留位1 | 1 | 空闲 |
| 预留位2 | 1 | 空闲 |
| delete_mask | 1 | 标记记录是否被删除(0未删除,1已删除),当标记为删除时没有真实从物理磁盘中删除,只是代表这块地址可以被覆写 |
| min_rec_mask | 1 | 标记记录是否为B+树的非叶子节点中的最小记录(索引时用到) |
| n_owned | 4 | 当前槽管理的记录数 |
| heap_no | 13 | 记录在堆中的相对位置 |
| record_type | 3 | 记录的类型,0表示普通记录,1表示B+树非叶节点记录,2表示最小记录,3表示最大记录 |
| next_record | 16 | 下一条记录的相对位置 |
| NULL值列表 | 用于标记和统一管理值为NULL的列 | |
| 变长字段长度列表 | 用于存储可变长度的字段的值占用的字节数 |
记录是以单向链表的方式存储的,因此:
思考一个问题:如果一个数据表的列字段很多或者存储的值太多,一个数据页存不完怎么办?在Compact行格式中,对于占用存储空间很大的列,在记录真实数据的地方只存储一部分数据,把剩余的数据分散存储在其它页中,通过在记录数据的尾部存储指向其它页数据的地址来关联这些分散的数据。
我们知道,数据库使用索引的目的是为了加快查询速度,那么MySQL是如何实现索引的呢?
对于数据行的插入,默认情况下是顺序存储(按主键排序)的,查询的时候将按照插入的顺序显示结果。当一个数据页存储字段较少且字段值内容较少的时候,很有可能这个数据页就能够同时存储上千行记录,前一节我们说过,数据是以单向链表的方式存储的,这种存储方式对于插入来说比较快,而对于查询来说就比较慢,如果我们需要使用where查询条件从上千行记录中获取满足条件的记录,如何才能加快查找速度呢?
InnoDB引擎为我们提供了一个叫做PageDirectory的页目录,这个概念在前面介绍数据页结构的时候提到过,它用于为数据页的行记录提供目录索引,类似书籍的章节目录,能够帮助我们快速定位数据记录所在的分组,从而加快查询速度,其实现原理是:将页内所有非删除的记录划分为N个组,每个组里最后一条记录(主键最大的记录)的n_owned属性记录了组内的记录数量,将这条记录的偏移地址取出按序从File Trailer位置开始向前写入形成PageDirectory,其中偏移地址称为'槽',图示如下:

根据上图所示.如果我们要获取id=4的记录,可以先通过二分法从目录页中快速找到这一记录所在的页,然后在快速定位数据记录所在的位置。
. 通过上面的解析,我们理解了从同一个数据页中快速查找记录的方式,再来想一想,如果数据行较多,分散存储到了多个数据页里,那又如何快速的确定数据在哪一个数据页的哪一个分组呢?
InnoDB为我们提供了"数据页"概念的同时,也给我们提供了一个叫做"目录页"的概念,目录页用于存储数据页的页号和页对应的记录的最小主键(相当存了一组{key,value}键值对集合,key主键,value为页号)。因此在执行条件查询时,先通过主键确定记录所在的页,在根据页内的页目录定位(通过主键定位)到分组,从而快速获取结果。结构图示如下:

分析结构图,发现结构是一棵B+树,B+树是一种树数据结构,通常用于数据库和操作系统的文件系统中。B+树的特点是能够保持数据稳定有序,其插入与修改拥有较稳定的对数时间复杂度。B+树元素自底向上插入,这与二叉树恰好相反。B+树的叶子节点用于存储真实数据,非叶子节点用于存储主键值和指针(页码)。
前面我们提到数据在插入时是按照主键的顺序来进行排序存储的,所以这就是为什么我们在设计表时建议设置主键自增的原因(新增记录时不会对之前的数据进行重新排列,这会加快插入的速度)。其实一个数据行字段除了我们自己创建的字段之外,还存在三个隐藏的字段row_id(行ID)、transaction_id(事务ID)、roll-pointer(回滚指针),如果我们设计表时没有设置主键,也没有设置唯一索引,那么它会自动以隐藏的字段row_id来作为自增主键进行排序存储。
思考一个问题:如果一个列的值占用空间较多,会发生什么?当一行记录存储的数据较多时,意味着要使用更多的数据页,更多的数据页意味着更多的目录页,这会增加" B+结构的高度 ",这也会隐形的降低查询效率。
到这里我们已经知道,对于主键字段来说,它会通过页目录和目录页的方式来增加数据查询速度,但是在实际开发中,我们可能还需要通过其他字段的查询条件来筛选数据,那么InnoDB引擎又是如何通过什么方式来增加查询速度的呢?
其实,我们也可以为非主键字段来创目录页,这些目录页同样组成了B+树结构,只不过它的叶子节点存储的是主键值,当通过非主键字段查询记录时,首先会通过非主键目录页B+树结构查找到主键,再去调用主键的目录页B+树,去查找真实数据。我们知道,除了可以为单独为非主键字段的某一个字段创建索引,还可以使用联合多个字段来创建一个索引,这两种方式的实现方式都是相同的。
根据前面的分析和探究,我们需要知道如下几点:
我们已经了解了索引的原理,知道了可以为表建立多种索引方式,来加快我们的查询速度,但是不同的索引类型的查询效率是有区别的,mysql有几种表的扫描机制:
在进行数据查询是应该尽量使用最小的扫描代价去获得结果,这是SQL性能优化的核心思想,需要注意的是并非避免全表扫描就能获得最佳效果,反之在具有多个索引的中,过多的索引组合反而可能导致效果不如全表扫描实在,这就是在创建索引时并非越多越好的原因。
Mysql给我提供了一个关键字explain用于分析和测试sql语句的性能,在SQL语句前增加该关键字,运行后会返回SQL执行计划的信息,通过这些信息,可以帮助我们更好的选择索引和优化SQL语句,例如运行如下语句得到结果如图:

其中,结果集的每个字段和可能的值说明如下:
| id |
查询语句的序号:相同ID:多条记录表示执行顺序由上而下;不同ID:如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行。 |
| select_type |
SIMPLE:简单的select查询,查询中不包含子查询或者UNION。 PRIMARY:查询中若包含任何复杂的子部分,最外层查询则被标记为PRIMARY。 SUBQUERY:在SELECT或WHERE列表中包含了子查询。 DERIUED:在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。 UNION:若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中外层SELECT将被标记为:DERIVED。 UNION RESULT: 从UNION表获取结果的SELECT。 |
| table | 表名:数据来自那张表。 |
| partitions | 分区表信息,没有分区表则为NULL。 |
| type | 访问类型指标,查询速度由快到慢:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL。 |
| possible_keys | 可能选择的索引,一个或多个。 |
| key | 实际使用的索引。如果为NULL,则没有使用索引。 |
| key_len | 选择的索引的长度,通常来说越小越好。 |
| ref | 和索引匹配的列。 |
| rows | 估算的扫描行数,越小越好。 |
| filtered | 被条件过滤行数的百分比。 |
| extra |
Using filesort:使了用临时表保存中间结果。 Using index:查询使用了索引。 Using where:使用了where过滤。 Using join buffer:使用了连接缓存。 impossible where:where子句的值总是false,不能用来获取任何数据。 |
我们已经了解了索引的原理,明白了数据页的存储结构是一棵B+树,同时目录页存储结构也是一棵B+树,能够通过索引定位数据所在的父节点,减少表的扫面,从而快速找到记录。但是当存在的索引越多时,查找数据的实现方案就越多,那如何才能尽量让我们编写的SQL尽量使用最优的方案呢?
文章头部的Mysql架构图中我们可以看到,我们编写的SQL语句在经过分析器进行语法分析后,传递给优化器,优化器帮助我们选择索引并生成执行计划,然后再交给执行器获取操作结果,但是优化器并非万能的,它只能为我们执行一些可控的优化。我们还需要从减少数据的碎片存储、不同类型数据的索引效率、表的扫面机制等方面考虑,特此总结出如下几个优化和设计规则:
SQL的执行总是有一个执行时间的,我们可以给数据库设置一个时间节点参数long_query_time,当SQL执行超过这个值时,便认为本次查询是慢查询,将被记录到慢查询日志中,有利于我们进行性能排查。默认的long_query_time默认值为10s。
开启慢查询日志:
[mysqld]
log-slow-queries=/data/mysqldata/slow-query.log #慢查询日志的位置
long_query_time=10 #表示查询超过10s就作为慢查询
set global slow_query_log='ON'; #开启慢查询
set global slow_query_log_file='/usr/local/slow.log'; #指定日志文件路径
set global long_query_time=10; #自定慢查询的时长
with RECURSIVE tmp as
(
select * from work_order_question where id=4
union all
select c.* from work_order_question as c,tmp t where c.parent_id=t.id
)
select id from tmp
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳
我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01 客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02 数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit
文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co
我正在尝试在Rails上安装ruby,到目前为止一切都已安装,但是当我尝试使用rakedb:create创建数据库时,我收到一个奇怪的错误:dyld:lazysymbolbindingfailed:Symbolnotfound:_mysql_get_client_infoReferencedfrom:/Library/Ruby/Gems/1.8/gems/mysql2-0.3.11/lib/mysql2/mysql2.bundleExpectedin:flatnamespacedyld:Symbolnotfound:_mysql_get_client_infoReferencedf
文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,
SPI接收数据左移一位问题目录SPI接收数据左移一位问题一、问题描述二、问题分析三、探究原理四、经验总结最近在工作在学习调试SPI的过程中遇到一个问题——接收数据整体向左移了一位(1bit)。SPI数据收发是数据交换,因此接收数据时从第二个字节开始才是有效数据,也就是数据整体向右移一个字节(1byte)。请教前辈之后也没有得到解决,通过在网上查阅前人经验终于解决问题,所以写一个避坑经验总结。实际背景:MCU与一款芯片使用spi通信,MCU作为主机,芯片作为从机。这款芯片采用的是它规定的六线SPI,多了两根线:RDY和INT,这样从机就可以主动请求主机给主机发送数据了。一、问题描述根据从机芯片手
前言一般来说,前端根据后台返回code码展示对应内容只需要在前台判断code值展示对应的内容即可,但要是匹配的code码比较多或者多个页面用到时,为了便于后期维护,后台就会使用字典表让前端匹配,下面我将在微信小程序中通过wxs的方法实现这个操作。为什么要使用wxs?{{method(a,b)}}可以看到,上述代码是一个调用方法传值的操作,在vue中很常见,多用于数据之间的转换,但由于微信小程序诸多限制的原因,你并不能优雅的这样操作,可能有人会说,为什么不用if判断实现呢?但是if判断的局限性在于如果存在数据量过大时,大量重复性操作和if判断会让你的代码显得异常冗余。wxswxs相当于是一个独立