范式和反范式

范式是符合某一种级别的关系模式的集合。关系数据库中的关系必须满足一定的要求,满足不同程度要求的为不同范式。目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、Boyce-Codd范式(BCNF)、第四范式(4NF)和第五范式(5NF)。满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多要求的称为第二范式(2NF),其余范式以次类推。一般说来,数据库只需满足第三范式(3NF)就行了。

第一范式

在任何一个关系数据库中,第一范式是对关系模式的基本要求,不满足第一范式的数据库就不是关系数据库。第一范式是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。在第一范式中表的每一行只包含一个实例的信息。

第一范式就是无重复的列。

第二范式

第二范式是在第一范式的基础上建立起来的。第二范式要求数据库表中的每个实例或行必须可以被唯一的区分。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识,这个唯一属性列称为关键字或主码、主键。

第二范式要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性。如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。

第二范式就是非主属性非部分依赖于主关键字。

第三范式

满足第三范式必须先满足第二范式。第三范式要求一个数据库表中不包含已在其他表中包含的非主关键字信息。

属性不依赖于其他非主属性,不存在非关键字对任一候选关键字的传递函数依赖。

BCNF:

在第三范式的基础上,不存在关键字段决定关键字段的情况,符合BCNF范式。

第四范式:

关系模型R< U,F >∈1NF,如果对于R的 每个非平凡多值依赖X→→Y(Y∉X),X都含有候选码,则R∈4NF。4NF就是限制关系模型的属性之间不允许有非平凡且非函数依赖的多值依赖。

第5范式:

最终范式,消除了4NF中的函数依赖。

范式的优点和缺点

范式有如下好处:

  • 范式化的更新操作通常比反范式化要快;
  • 当数据较好的范式化时,就只有很少或者没有重复数据,所以只需要修改更少的数据;
  • 范式化的表通常更小,可以更好地放在内存里,所以执行操作会更快;
  • 很少有多余的数据意味着检索列表数据时更少需要DISTINCT或者GROUP BY语句。

范式化设计的schema的缺点是通常需要关联,稍微复杂一些的查询语句在符合范式的schema上都可能需要至少一次关联,也许更多。这不但代价昂贵,也可能使一些索引策略无效。例如,范式化可能将列存放在不同的表中,而这些列如果在一个表中本可以属于同一个索引。

反范式的优点和缺点

反范式化的schema因为所有数据都在一张表中,可以很好地避免关联。

如果不需要关联表,则对大部分查询最差的情况——即使表没有使用索引——是全表扫描。当数据比内存大时这可能比关联要快得多,因为这样避免了随机I/O。单独的表也能因为没有关联使用更有效的索引策略。

混用范式化和反范式化

范式化和反范式化的schema各有优劣。在实际应用中经常需要混用,可能使用部分范式化的schema、缓存表以及其他技巧。

最常见的反范式化数据的方法是复制或者缓存,在不同的表中存储相同的特定列。在MySQL5.0和更新版本中,可以使用触发器更新缓存值,这使得实现这样的方案变得更简单。

缓存表和汇总表

有时提升性能最好的方法是在同一张表中保存衍生的冗余数据,然而,有时也需要创建一张完全独立的汇总表或缓存表(特别是为满足检索的需求时)。如果能容许少量的脏数据,这是非常好的方法,但是有时确实没有选择的余地(例如,需要避免复杂、昂贵的实时更新操作)。

术语“缓存表”表示存储那些可以比较简单的从schema其他表获取数据的表(例如,逻辑上冗余的数据)。“汇总表”保存的是使用GROUP BY语句聚合数据的表(例如,数据不是逻辑上冗余的)。

以网站为例。假设需要计算之前24小时内发送的消息数,在一个很繁忙的网站不可能维护一个实时精确的计数器,作为替代方案,可以每小时生成一张汇总表,这样也许一条简单的查询就可以做到,并且比实时维护计数器要高效的多,缺点是并不是100%精确。

如果必须获得过去24小时准确的消息发送数量,有另外一种选择。以每小时汇总表为基础,把前23个完整的小时的统计表中的计数全部加起来,最后再加上开始阶段和结束阶段不完整的小时内的计数。

不管是哪种方法,都比计算message表的所有行要有效的多,这是建立汇总表的最关键原因。实时计算统计值是很昂贵的操作,因为要么需要扫描表中的大部分数据,要么查询语句只能在某些特定的索引上才能有效运行,而这类特定索引一般会对UPDATE操作有影响,使以一般不希望创建这样的索引。计算最活跃的用户或者最常见的标签是这种操作的典型例子。

缓存表则相反,其对优化搜索和检索查询语句很有效。这些查询语句经常需要特殊的表和索引结构,跟普通OLTP(联机事务处理)操作用的表有些区别。

例如,可能会需要很多不同的索引组合来加速各种类型的查询。这些矛盾的需求有时需要创建一张只包含主表中部分列的缓存表。一个有用的技巧是对缓存表使用不同的存储引擎,充分利用各种存储引擎的优点。

在使用缓存表和汇总表时,必须决定是实施维护数据还是定期重建,哪个更好依赖于应用程序。定时重建可以节省资源,也可以保持表不会有很多碎片,以及有完全顺序组织的索引。

当重建汇总表和缓存表时,通常需要保证数据在操作时依然可用,这就需要通过使用“影子表”来实现。影子表指的是在一张真实表背后创建的表,当完成了建表操作后,可以通过一个原子的重命名操作切换影子表和原表。例如,如果需要重建my_summary,可以先创建my_summary_new,然后填充好数据,最后和真实表做切换:

DROP TABLE IF EXISTS my_summary_new,my_summary_old;
CREATE TABLE my_summary_new LIKE my_summary;
RENAME TABLE my_summary TO my_summary_old,my_summary_new TO my_summary;

如上,可以在下一次重建之前保留旧版本的数据,如果新表有问题,可以很容易的进行快速回滚操作。

计数器表

如果应用在表中保存计数器,则在更新计数器时可能碰到并发问题。计数器表在Web应用中很常见,可以用这种表缓存一个用户的朋友数、文件下载次数等。创建一张独立的表存储计数器通常是个好主意,这样可以使计数器表小且快。使用独立的表可以帮助避免查询缓存失效,并且可以使用一些更高效的技巧。

假设有一个计数器表记录网站的点击次数:

CREATE TABLE hit_counter(cnt int unsigned not null
)ENGINE=InnoDB;

对于任何想要更新这一行的事务来说,这条记录上都有一个全局的互斥锁(mutex)。这会使得这些事务只能串行执行。要获得更高的并发更新性能,也可以将计数器保存在多行中,每次随机选择一行进行更新,如下:

CREATE TABLE hit_counter(slot tinyint unsigned not null primary key,cnt int unsigned not null
)ENGINE=InnoDB;

预先在上表中增加100行数据,更新时选择一个随机的槽进行更新:

UPDATE hit_counter SET cnt = cnt + 1 WHERE slot = RAND() * 100;

获取统计结果:

SSELET SUM(cnt) FROM hit_counter;

一个常见的需求是每隔一段时间开始一个新的计数器(如,每天一个),可以这样修改表设计:

CREATE TABLE daily_hit_counter(day date not null,slot tinyint unsigned not null,cnt int unsigned not null,primary key(day,slot)
)ENGINE=InnoDB;

在这个例子中,可以不用预先生成行,用ON DUPLICATE KEY UPDATE代替:

INSERT INTO daily_hit_counter(day,slot,cnt)VALUES(CURRENT_DATE,RAND()*100,1)ON DUPLICATE KEY UPDATE cnt = cnt+1;

如果希望减少表的行数,以避免表变得太大,可以写一个周期执行的任务,合并所有结果到0号槽,并且删除所有其它的槽:

UPDATE daily_hit_counter as cINNER JOIN(SELECT day,SUM(cnt) AS cnt,MIN(slot) AS mslotFROM daily_hit_counterGROUP BY day)AS x USING(day)
SET c.cnt = IF(c.slot = x.mslot,x.cnt,0),c.slot = IF(c.slot = x.mslot,0,c.slot);DELETE FROM daily_hit_counter WHERE slot <> 0 AND cnt = 0;

加快ALTER TABLE操作的速度

MySQL的ALTER TABLE操作的性能对大表来说是个大问题。MySQL执行大部分修改表结构操作的方法是用新的结构创建一个空表,从旧表中查出所有数据插入新表,然后删除旧表,这样操作可能要花费很长时间,如果内存不足而表又很大,而且还有很多索引的情况下尤其如此。

MySQL5.1以及更新版本包含一些类型的“在线”操作的支持,这些功能不需要在整个操作过程中锁表。最近版本的InnoDB也支持通过排序来建索引,这使得建索引更快并且有一个紧凑的索引布局。

一般而言,大部分ALTER TABLE操作将导致MySQL服务中断。对常见的场景,可以先在一台不提供服务的机器上执行ALTER TABLE操作,然后和提供服务的主库进行切换,另一种技巧是“影子拷贝”,用要求的表结构创建一张和源表无关的新表,然后通过重命名和删表操作交换两张表。

不是所有的ALTER TABLE操作都会引起重建,有两种方法可以改变或者删除一个列的默认值(一种很快,另一种很慢)。假如要修改电影的默认租赁期限,从三天改到五天,下面是很慢的方式:

ALTER TABLE film
MODIFY COLUMN rental_duration TINYINT(3) NOT NULL DEFAULT 5;

上述代码拷贝了整张表到一张新表,甚至列的类型、大小和可否为NULL属性都没改变。理论上,MySQL可以跳过创建表的步骤,列的默认值实际上存在表的.frm文件中,所以可以直接修改这个文件而不需要改动表本身。然而MySQL还没有采用这种优化的方法,所有的MODIFY COLUMN操作都将导致表重建。

另一种方法是通过ALTER COLUMN操作来改变列的默认值:

ALTER TABLE film
ALTER COLUMN rental_duration SET DEFAULT 5;

这个语句会直接修改.frm文件而不涉及表数据,这个操作是非常快的。

只修改.frm文件

只修改表的.frm文件是很快的,但MySQL有时候会在没有必要的时候也重建表。如果愿意冒一些风险,可以让MySQL做一些其他类型的修改而不用重建表。

下面这些操作是有可能不需要重建表的:

  • 移除一个列的AUTO_INCREMENT属性;
  • 增加、移除或更改ENUM和SET常量。如果移除的是已经有行数据用到其值的常量,查询将会返回一个空字串值。

基本的技术是为想要的表结构创建一个新的.frm文件,然后用它替换掉已经存在的那张表的.frm文件:

  • 创建一张有相同结构的空表,并进行所需要的修改;
  • 执行FLUSH TABLES WITH READ LOCK,这将会关闭所有正在使用的表,并且禁止任何表被打开;
  • 交换.frm文件;
  • 执行UNLOCK TABLES释放读锁。

快速创建MyISAM索引

为了高效的载入数据到MyISAM表中,有一个常用的技巧是先禁用索引、载入数据,然后重新启用索引:

ALTER TABLE load_data DISABLE KEYS;
ALTER TABLE load_data ENABLE KEYS;

这个技巧能够发挥作用,是因为构建索引的工作被延迟到数据完全载入以后,这个时候已经可以通过排序来构建索引了。这样做会快很多,并且使得索引树的碎片更少、更紧凑。

这个方法对唯一索引无效,因为DISABLE KEYS只对非唯一索引有效,MyISAM会在内存中构造唯一索引,并且为载入的每一行检查唯一性。一旦索引的大小超过了有效内存大小,载入操作就会变得越来越慢。

在现代版本的InnoDB中,有一个类似的技巧,这依赖于InnoDB的快速在线索引创建功能。这个技巧是,先删除所有的非唯一索引,然后增加新的列,最后重新创建删除掉的索引。Percona Server可以自动完成这些操作步骤。

当已经知道所有数据都是有效的并且没有必要做唯一性检查时可以这样操作进行加速:

  • 用需要的表结构创建一张表,但是不包括索引;
  • 载入数据到表中以构建.MYD文件;
  • 按照需要的结构创建另外一张空表,这次要包含索引,这会创建需要的.frm和.MYI文件;
  • 获取读锁并刷新表;
  • 重命名第二张表的.frm和.MYI文件,让MySQL以为是第一张表的文件;
  • 释放读锁;
  • 使用REPAIR TABLE来重建表的索引,该操作会通过排序来构建所有索引,包括唯一索引。

Mysql Schema优化相关推荐

  1. MySQL调优(二):数据类型和schema优化,MySQL8.0取消查询缓存的原因

    数据类型和schema优化 数据类型的优化 合理使用范式和反范式 三大范式: 1.表不可分 2.不能存在传递依赖 3.表里其他列的值必须唯一依赖于主键 约定大于规范,没有必要严格遵守范式,以业务为准, ...

  2. mysql schema数据混乱_MySQL之Schema与数据类型优化

    选择优化的数据类型 MySQL支持的数据类型非常多,选择正确的数据类型对于获得高性能至关重要.不管存储哪种类型的数据,下面几个简单的原则都有助于做出更好的选择: 更小的通常更好 一般情况下,应该尽量使 ...

  3. MySQL(二)数据类型和schema优化

    数据类型和schema优化 数据类型的优化 更小的通常更好 <高性能MySQL> 应该尽量使用可以正确存储数据的最小数据类型,更小的数据类型通常更快,因为它们占用更少的磁盘.内存和CPU缓 ...

  4. MySQL全面优化,速度飞起来

    在进行MySQL的优化之前,必须要了解的就是MySQL的查询过程,很多查询优化工作实际上就是遵循一些原则,让MySQL的优化器能够按照预想的合理方式运行而已. 图-MySQL查询过程 一.优化的哲学 ...

  5. MySQL 性能优化之高阶神技

    一.前言 MySQL调优对于很多程序员而言,都是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思路不清晰. 在进行MySQL的优化之前必须要了解的就是MySQL的查询过程,很多的查询 ...

  6. 史上最全的MySQL高性能优化实战总结!

    https://www.jianshu.com/p/4af41b682e06 1.1 前言 MySQL对于很多Linux从业者而言,是一个非常棘手的问题,多数情况都是因为对数据库出现问题的情况和处理思 ...

  7. MySQL全面优化,速度飞起来!

    作者:惨绿少年 https://www.cnblogs.com/clsn/p/8214048.html 在进行MySQL的优化之前,必须要了解的就是MySQL的查询过程,很多查询优化工作实际上就是遵循 ...

  8. MySQL性能优化之必备技能【推荐】

    导读:MySQL 是目前广泛使用的数据库,但很多项目对 MySQL 的使用仍然存在欠优化的地方,本文根据作者长年的经验提出了 MySQL 数据库优化方法,这些方法是否适合你的项目?还有哪些优化方法值得 ...

  9. 学习 MySQL 高性能优化原理,这一篇就够了!

    来自:CHEN川 | 责编:乐乐 链接:dwz.cn/VKMCdWla 说起 MySQL 的查询优化,相信大家收藏了一堆奇技淫巧:不能使用 SELECT *.不使用 NULL 字段.合理创建索引.为字 ...

最新文章

  1. EasyUI环境搭建与入门基础语法
  2. MySQL 5.7基于GTID及多线程主从复制
  3. matlab中数组创建方法
  4. 浏览器用户脚本管理器(Tampermonkey)
  5. linux内核安装教程,Linux内核5.9的最重要功能及安装方法
  6. 召唤新一代超参调优开源新神器,集十大主流模块于一身
  7. Linux 完全卸载重装opencv
  8. web前后台数据交互的四种方式
  9. 云智能资深专家崮德:谈谈我对华为HarmonyOS 2.0的看法
  10. scrapy命令介绍
  11. php ctf题,CTF---PHP安全考题
  12. 视觉3d中五折幕的震撼这就是沉浸式屏幕
  13. 快速理解Raft之日志复制(肝了两千五百字)
  14. SQL--Transact-SQL
  15. 游戏工作室的六种赚钱方法
  16. 【Maven】创建模块时出现Invalid packaging for parent POM
  17. java8 Predicate
  18. python request.get
  19. Python报错:'dict' object has no attribute 'iteritems' 的解决方案
  20. uni-app中使用rich-text如何添加样式控制富文本里面的内容

热门文章

  1. Unity3D ParticleSystem粒子系统属性简介
  2. C语言为什么不执行数组下标的有效性检查
  3. Redis订阅与发布原理
  4. 基础30讲 第九讲 一元函数积分学的几何应用
  5. 目前最火的人工神经网络,神经网络软件有哪些
  6. 企业微信公众号运营技巧有哪些
  7. ESP32开发路程WIFI篇——极简连接WIFI,模拟设备连接阿里云,ESP32连接阿里云
  8. Win10自带的录屏功能怎么用?
  9. pyMuPDF How To
  10. 每天学命令deletePlaceBlockage