`
风雪涟漪
  • 浏览: 508602 次
  • 性别: Icon_minigender_1
  • 来自: 大连->北京
博客专栏
952ab666-b589-3ca9-8be6-3772bb8d36d4
搜索引擎基础(Search...
浏览量:9069
Ae468720-c1b2-3218-bad0-65e2f3d5477e
SEO策略
浏览量:18481
社区版块
存档分类
最新评论

Schema的优化和索引 - 范式和非范式

阅读更多

有很多方法来展现给定的数据。从完全范式到完全的非范式以及介于两者之间。在符合范式的数据库中,每个事实展现一次并且仅仅展现一次而已。相反的,在非范式的数据库,信息重复或者存储在很多的地方。

 

如果你不熟悉范式,你应该加强学习了。关于范式,你可以通过一些书和网上资源来学习。在这里,我们主要介绍这一章中你应该明白的知识。让我们看看经典的例子,那就是employee,departments,和department heads.

 

 

EMPLOYEE             DEPARTMENT                  HEAD

Jones                      Accounting                       Jones

Smith                      Engineering                     Smith

Brown                     Accounting                       Jones

Green                     Engineering                      Smith

 

这种设计问题在于当数据更改的时候,这数据模型就会变得不正常了。如果Brown接管了Accounting部门,我们必须更新多条语句来反映出这个改变,并且这些更新可能还使数据的状态不一致。如果Jones行的HEAD和Brown行的HEAD不同的话,没有方法知道到底那个HEAD是正确的。就像那句老话一样:一个人有两块表,就不会知道准确的时间。更进一步的说,没有员工的时候就不能展现DEPARTMENT.如果我们删除了全部员工,DEPARTMENT的信息也同样的被删除了。为了避免这个问题,我们把这个表分为EMPLOYEE和DEPARTMENT两个实体。结果为两张表:

 

 

EMPLOYEE_NAME        DEPARTMENT

Jones                           Accounting

Smith                           Engineering

Brown                          Accounting

Green                           Engineering

 

 

 

DEPARTMENT                 HEAD

Accounting                     Jones

Engineering                   Smith

 

 

这些表属于第二范式,对于大部分需求已经足够了。第二范式只是众多范式的其中一个。

 

一个范式数据模型的缺点和优点

那些对性能要求较高的需求,推荐用范式化的数据模型。尤其对于写操作较多的需求。使用范式化模型的好处如下:

 

  • 范式化的更新速度要快于非范式化的更新。
  • 当数据很好的范式化之后,就有很少或者没有重复的数据。因此也就会有很少的数据需要更新。
  • 范式化的表常常很小,因此它们可以放到内存中,使性能变得更好。
  • 没有多于的数据就意味着,当获取值的列表的时候,就不需要太多的DITINCT或者GROUP BY 的查询。考虑下以前的例子:如果不使用DISTINCT或者GROUP BY在非范式化的表查询唯一列表的部门是不可能的,但是如果DEPARTMENT是独立表,仅仅是个获取查询而已。
范式化数据模型的缺点就是获取数据。在很好的范式化模型上做一些复杂的查询,至少都需要连接一张表,也可能更多。这样的消耗不仅很大,并且使有些索引策略变为不可能。比如,范式化把列放到不同的表中,这些表都会从相同的索引中得到好处。


非范式化数据模型的缺点和优点

一个非范式化的模型可能工作的很好,以为任何数据都放在相同的表中,这样避免了连接。

 

如果你不需要连接表,来看个最坏情况下的查询-即使没有使用索引,那就是个全表的扫描。在数据没有在内存中的时候,这样的查询都要快于连接表的查询。因为它避免了随机的IO。

 

一个单独的表也允许使用更高效的索引策略。假使你有个用户发送消息的网站,并且一些用户是付费用户。现在你想查看来自付费用户最新的10条信息。如果你已经范式化数据模型了并且索引了message的发布时间,这个查询可能如下:

 

 

mysql> SELECT message_text, user_name

    -> FROM message

    -> INNER JOIN user ON message.user_id=user.id

    -> WHERE user.account_type='premium'

    -> ORDER BY message.published DESC LIMIT 10;

 

 

这个查询的执行是有效率的,Mysql需要会扫描published索引。对于找到的每一行,它还需要到查看user表并且检查这个user是否是付费用户。如果只有一小部分帐户是付费的,那么这样的查询效率就低下了。另一个可能的查询是选择所有的付费用户,然后在获取它们所有的信息,做一个文件排序。这可能更糟糕。。

 

问题就出在连接上,这样你就不能在一个索引上使用排序和条件过滤。如果你非范式化这些数据,把这两张表整合并且在(account_type,published)添加索引,你就能写出一个不需要连接的查询。这个查询非常高效。

 

 

mysql> SELECT message_text,user_name

    -> FROM user_messages

    -> WHERE account_type='premium'

    -> ORDER BY published DESC

    -> LIMIT 10;

 

 

 

范式化和非范式化的混合使用

我们知道了范式化和非范式化的优点和缺点,那么怎样才能做到最佳的设计呢?

 

事实就是,完全范式和完全非范式的都像实验室的小白鼠:现实中,它们能做的非常少。在现实中,你需要混合这两种方式,可能使用一部分范式化模型,缓存表,和其他技术。

 

最常用的非范式化数据的方法是复制或者缓存,选择列从一张表到另一张表。在MySQL5.0以上版本,你可以使用触发器来更新缓存数据。这样实现起来也很简单。

 

在我们网站这个例子中,可以把account_type存储在user和message表中,这个方法可以替代上次说的那个完全非范式化的方法。这样做可以避免完全非范式化所引起的INSERT和DELETE的问题,因为即使没有MESSAGE也不会丢失USER的信息。它也不会使user_message表变得更大,还会使查询数据更为高效。

 

然而,这样做会使更新account_type消耗更大。因为你需要更新两张表。要知道这是否是个问题,你必须要考虑的是这些更改的频率和多久会变化,在比较SELECT查询的频率。

 

把一些数据从父表移动到子表还有一个好处就是为了排序。举个例子,如果按照author名字来排序message的话,你可以把author_name在message表中缓存和索引,这样的查询语句是非常高效的。

 

这对于一些衍生数据的缓存也非常有用。如果你需要显示每个用户有多少条信息,你可以使用子查询来计算这些数据,也可以在user表中添加一个num_message字段,当user发布一个message的时候再更新它。

 

 

缓存和汇总表(Cache and Summary tables)

有的时候提升性能最好的方法就是在相同的表中存放冗余的数据。然而,有的时候,你可能需要创建单独的汇总或缓存表来优化查询。如果你能容忍少量过时的数据,这种方法是最佳的了,但是有的时候你真的没有选择。

 

关于缓存表和汇总表都是不标准的说法。如果我们提到缓存表就意味着表中包含的数据可以很容易的获取(数据是逻辑上的冗余)。当我们说道汇总表的时候,意思就是通过GROUP BY来聚合数据(数据不是逻辑上的冗余)。一些人也叫这些表为“roll-up tables”。因为数据是不断累积的。

 

继续回到上一个web站点的例子,假使你要计算24小时之前的message之和。在一个访问量很大的网站,实时精确的值是不太可能的。我们可以每小时生成一个汇总表。这样完成这个需求只需要一个查询语句,并且这也比维护一个累加字段更高效。缺点就是这个数值并不是100%的精确。

 

如果你需要一个精确的数值,还有另一个选择。你可以把那个时间段中的23小时的的,开始时间段的部分小时的,还有时间段结束的部分小时的message数相加来得到准确的值。假使你的汇总表叫做msg_per_hr并且定义如下

 

CREATE TABLE msg_per_hr (
   hr DATETIME NOT NULL,
   cnt INT UNSIGNED NOT NULL,
   PRIMARY KEY(hr)
);

 

你可以把下列三个查询求和来计算过去24小时message的数。

 

mysql> SELECT SUM(cnt) FROM msg_per_hr
    -> WHERE hr BETWEEN
    ->    CONCAT(LEFT(NOW( ), 14), '00:00') - INTERVAL 23 HOUR
    ->    AND CONCAT(LEFT(NOW( ), 14), '00:00') - INTERVAL 1 HOUR;
mysql> SELECT COUNT(*) FROM message
    -> WHERE posted >= NOW( ) - INTERVAL 24 HOUR
    ->    AND posted < CONCAT(LEFT(NOW( ), 14), '00:00') - INTERVAL 23 HOUR;
mysql> SELECT COUNT(*) FROM message
    -> WHERE posted >= CONCAT(LEFT(NOW( ), 14), '00:00');

 

 

不管是准确的计算和不准确的计算都要比计算所有message表的行数要高效很多。关键原因就在于创建了汇总表。这种统计如果实时统计消耗非常大,因为需要扫描的数据太多了,再就是查询所需要的索引,你可能也不会添加,因为这样太影响INSERT和UPDATE了。计算最活跃的用户和频率最高的TAG都是这样的操作。

 

来说说缓存表,它对于优化搜索和查询的语句非常有用。这些查询一般都需要特定的表和索引结构,这索引结构和应用于一般的OLTP操作的索引结构是不同的。

 

举个例子,你可能需要不同的索引组合来提升不同种类查询的性能。这些相互冲突的需求有的时候需要你创建一个包含主表一些列的缓存表。一个有用的技术就是对缓存表使用不同的存储引擎。如果主表使用的是INNODB,而对缓存表使用的是MyISAM,那么就可以使索引更小,并且可以做全文搜索的查询。有的时候你可能想让表完全脱离MySQL,可以使用特殊的系统来提升查询性能,比如Lucene或者Sphinx搜索引擎。

 

当使用缓存和汇总表,必须要决定是否要实时维护数据或者周期性重建数据。哪种更好完全取决与你的应用程序,但是周期性重建数据不仅仅可以节省资源,还可以使结果存放在更有效率的表中,这些表不会碎片并且含有完全排序的索引。

 

当你重建汇总和缓存表的时候,在操作的时候你常常需要它们的数据保持可见。你可以使用“shadow table”(影像表)来实现。当你已经创建它之后,你可以使用原子性的重命名来交换这些表。举个例子,如果你需要重建my_summary,你能创建my_summary_new,填充数据,把它和真正的表作交换。

 

mysql> DROP TABLE IF EXISTS my_summary_new, my_summary_old;
mysql> CREATE TABLE my_summary_new LIKE my_summary;
-- populate my_summary_new as desired
mysql> RENAME TABLE my_summary TO my_summary_old, my_summary_new TO my_summary;

 

在赋予新建表my_summary名之前,如果你把my_summary重命名为my_summary_old,你就可以保存这个表老的版本直到下次这个表的重建。如果新表出现问题,你也可以用旧的来替换。

计数表(Counter tables)

一个应用程序在一个表中存放计数字段,当更新计数字段的时候,会遇到并发问题。这些表在web应用程序中很常见。你能使用它们来缓存用户的好友数,文件的下载数等等。一般来说,创建一个单独的表来存放这些计数是个好的主意,这样做能使它更小更快。使用单独的表,可以避免无效的查询缓存并且让你使用更多的高级技术。

 

为了让例子更简单,假使你有个包含一列的计数表。来记录你网站的点击量。

 

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

 

每次点击都更新计数列。

 

mysql> UPDATE hit_counter SET cnt = cnt + 1;

 

这样做问题在于这个单独的行是对于任意更新计数的事物是全局互斥的。它会序列化这些事物。为了获得更高的并发,可以保存更多的行并且随机更新这些行。表的修改如下

 

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

 

预先给这个表添加100行,现在这个查询就随机更新计数了。

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

 

为了获取统计信息,可以使用聚合查询

 

mysql> SELECT SUM(cnt) FROM hit_counter;

 

还有一个一般需求就是需要每个周期的计数。(比如,一天一次)。如果你需要这么做,你可以修改下数据模型。

 

mysql> 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:

 

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

 

 

如果你想减少一些行来使表变得更小,你可以写一个周期性的任务来把合并所有的结果到slot()并且上除其他的slot.

 

mysql> UPDATE daily_hit_counter as c
    ->    INNER JOIN (
    ->       SELECT day, SUM(cnt) AS cnt, MIN(slot) AS mslot
    ->       FROM daily_hit_counter
    ->       GROUP 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);
mysql> DELETE FROM daily_hit_counter WHERE slot <> 0 AND cnt = 0;

 

 

2
0
分享到:
评论

相关推荐

    mysql面试题-mysql经典面试题目-数据库的基本概念-SQL语法-事务处理-索引优化-性能调优-mysql-面试题目

    性能监控和调优工具,如MySQL的Performance Schema和InnoDB Monitor,帮助识别性能瓶颈并优化数据库设置。 数据库连接字符串是用于连接数据库的应用程序配置,包含数据库地址、端口、用户名、密码等信息。 视图的...

    ssd7 exercise7 标准答案

    同时,这也涉及到数据库性能优化的概念,比如索引的使用、适当的数据类型选择以及良好的范式设计,以减少数据冗余并提高查询效率。 总的来说,"SSD7 Exercise7"提供了实践和巩固数据库设计和SQL技能的机会。通过...

    SQL语句-sql语句转换成ent-schema.zip

    7. **规范化和优化**:根据数据库设计原则,如范式理论(1NF, 2NF, 3NF, BCNF等),可能需要进一步优化Ent-Schema,以减少数据冗余和提高查询效率。 8. **绘制Ent-Schema图**:最后,使用专业工具(如ERD工具)将...

    Mysql性能优化教程.doc

    - 利用INFORMATION_SCHEMA.TABLES和INFORMATION_SCHEMA.PROCESSLIST视图获取更多信息。 #### 五、分析流程 - **步骤概述**: - 确定问题: 识别查询性能不佳的原因。 - 收集信息: 使用EXPLAIN和其他工具收集查询...

    数据库教程 西北工业大学

    6. **性能调优**:探讨索引的创建与管理,查询优化,以及如何使用SQL Server Profiler监控和分析数据库性能。 7. **备份与恢复**:阐述SQL 2000的备份类型和恢复模式,以及在灾难情况下的数据恢复策略。 8. **...

    mongodb_架构设计基础schemadesign-cn

    - **索引 vs 索引**:索引在两种类型的数据库中都用于加快查询速度,但在MongoDB中,索引可以更具体地针对文档中的字段创建,甚至可以创建复合索引和地理空间索引等。 - **联接 vs 文档间嵌入与链接**:在RDBMS中,...

    MySQL数据库高效使用规范.docx.zip_MYSQL_mysql 优化_mysql优化

    - **调整内存参数**:如key_buffer_size、innodb_buffer_pool_size等,确保足够的内存用于缓存数据和索引。 - **优化连接管理**:合理设置max_connections,避免过多的空闲连接占用资源。 - **慢查询日志**:启用...

    MySQL优化大揭秘.rar

    MySQL优化是数据库管理中至关重要的一个环节,它旨在提高数据查询效率、降低系统资源消耗,以确保服务的稳定性和响应速度。"MySQL优化大揭秘"这个压缩包中的"SQL.pdf"很可能包含了关于如何优化MySQL数据库的详细教程...

    数据库编程(九)- MySQL优化

    - 数据规范:遵循第三范式(3NF),减少数据冗余,降低更新异常和插入异常的风险。 - 分区与分表:对于大量数据,可使用分区或分表策略,将大表分成更小、更易管理的部分。 2. **查询优化**: - 使用EXPLAIN分析...

    MySQL性能优化

    MySQL性能优化是一个涵盖广泛的主题,涉及数据库架构设计、查询优化、索引策略、存储引擎选择、资源管理和配置等多个方面。以下是一些关键知识点的详细解释: 1. **查询优化**: - **EXPLAIN分析**:通过EXPLAIN...

    高效MySQL查询加速指南:索引策略、查询优化、性能调优,助力数据库管理员和开发者突破性能瓶颈

    ### 高效MySQL查询加速指南:索引策略、查询优化、性能调优 在数据库管理和开发领域中,MySQL因其灵活性和高效性而受到广泛欢迎。然而,在面对大规模数据集时,即使是性能强大的MySQL也可能遇到查询速度慢的问题。...

    MySchool数据库设计优化

    还可以利用数据库内置的性能监视工具,如MySQL的Performance Schema或SQL Server的Dynamic Management Views,收集和分析性能数据,以便进行针对性的优化。 最后,数据库备份和恢复策略不容忽视。定期备份数据库,...

    MySQL性能优化详解.docx

    在实际应用中,标准化和非标准化的结合使用更为常见。 - **数据类型**: 选择合适的数据类型是优化表结构的关键。例如,尽量使用数值类型而不是字符串类型来存储数字,尽可能使用更小的整型(如`MEDIUMINT`而非`INT`...

    大牛讲解的MySQL介绍及性能优化 PPT

    - 索引优化:理解B-TREE、HASH索引的工作原理,何时使用全文索引,以及如何通过EXPLAIN分析查询性能。 - 查询优化:避免全表扫描,合理使用LIMIT,减少子查询,优化JOIN操作。 - 表设计:范式理论,选择合适的...

    【MySQL技术资料】-(机构内训资料)MySQL优化学习思维笔记

    了解二进制日志(binlog)和查询日志的用途,以及如何通过Performance Schema和pt-query-digest等工具进行性能监控。 7. **分区与分片**:大型数据库可能需要分区或分片来分散负载。理解如何基于时间、范围、哈希等...

    十三MySQL性能优化详解.pdf

    MySQL 性能优化详解 ...MySQL 的性能优化是一个复杂的任务,需要从多方面考虑,包括数据库设计、查询优化、服务器端优化和存储引擎优化。只有通过合理的优化,才能提高 MySQL 的性能,满足实际应用的需求。

    java开发面试常见的数据库基础相关的技术点介绍以及面试问题解答

    聚簇索引(主键索引,包含完整数据)和二级索引(辅助索引,不含完整数据);单列索引、联合索引。 5. **索引优化**: - **选择合适的索引类型**:根据查询需求,选择B+树或Hash索引。 - **避免全表扫描**:合理...

    mysql学习文档从基础到调优再到高可用案例详解

    - 聚集索引与非聚集索引:InnoDB中的主键索引是聚集索引,其他索引是非聚集索引。 3. **SQL语言基础**: - DDL(Data Definition Language):创建、修改、删除数据库对象。 - DML(Data Manipulation Language...

Global site tag (gtag.js) - Google Analytics