MySQL如何设计最优的库表结构

牛客网面经:1、一张大表怎么更改表的数据结构,字段,用alter会有什么问题,怎么解决呢,有什么好的方案?

如何设计最优的库表结构

设计表结构的时候我们应该遵守哪些原则,需要考虑哪些因素?我们先不讨论索引相关内容,本文为《高性能MySQL》第4章读书笔记,并回答牛客网面经上的面试题。

选择数据类型

三个基本原则:

  • 更小的通常更好
    • 尽量使用可以正确存储数据的最小数据类型,但是要确保没有低估需要存储的值的范围,因为在schema中的多个地方增加数据类型的范围(可能不是改范围,而是改类型)是一个非常耗时和痛苦的操作。如果无法确定哪个数据类型是最好的,就选择我们认为不会超过范围的最小类型
  • 简单就好
    • 简单的数据类型的操作可以使用更少的CPU周期。例如,整型比字符操作代价低
  • 尽量避免NULL
    • 很多表都包含可为NULL的列,是因为可为NULL是列的默认属性,除非我们指定列为NOT NULL。
    • 可为NULL的列是索引、索引统计和值比较都更复杂,所以查询中包含可为NULL的列时更难优化
    • 将可为NULL的列改为NOT NULL带来的性能提升比较小,不用刻意修改已有表结构。但是,如果要建索引,应该尽量避免建在可为NULL的列上

先确定合适的大的数据类型范围:数字、字符串、时间等,再根据三个基本原则选择具体的数据类型。

在确定具体的数据类型时,需了解类似的数据类型之间的区别,可以参考另一篇文章:数据类型注意项

范式和反范式

1、在范式化的数据库中,每个事实数据会出现且只出现一次。相反,在反范式化的数据库中,信息是冗余的,可能回存储在多个地方。

2、范式的优缺点

  • 优点
    1.范式化的更新操作通常比反范式化要快
    2.只有很少或者没有重复数据,所以只需要修改更少的数据
    3.范式化的表通常更小,可以更好地放在内存中,所以执行操作会更快。
    4.很少有多余的数据意味着检索列表数据时更少需要 DISTINCT 或者 GROUP BY 语句
  • 缺点
    1.通常复杂些的查询时需要关联
    2.可能会使一些索引策略无效,因为不同表中的列若在一个表中本可以属于同一个索引来优化查询

3.反范式的优缺点

  • 优点
    1.很好的避免关联,如果不需要关联,则大部分查询最差情况使全表扫描(没有使用索引)。当数据比内存大时这可能比关联要快得多
    2.由于冗余部分字段,可使用这些字段创建更有效的索引策略

3、混用范式化和反范式化
在实际应用中经常混用。最常见的反范式化数据的方法是复制和缓存,在不同的表中存储相同的特定列,可以使用触发器更新缓存值。另一个从 a 表冗余一些数据到 b 表的理由是排序的需要,需要用于排序的多个字段分布在不同表上,难以很好实现

缓存表和汇总表

“缓存表”表示存储那些可以比较简单地从其他表获取数据的(冗余)表;“汇总表”保存的是使用 GROUP BY 等语句聚合数据的表。

缓存表,对优化搜索和查询有效,一个有用的技巧是对缓存表使用不同的存储引擎

汇总表建立的最关键原因是实时计算统计值是很昂贵的操作(大概率扫描表中大部分数据)

使用缓存表和汇总表时,需要决定是实时维护数据还是定期重建。如果选择定期重建且需要保证数据在操作是依然可用,就需要使用“影子库”来实现。

参见应用的缓存表场景:物化视图,https://www.cnblogs.com/hkdpp/p/8302657.html

常见应用的汇总表场景:计数器表,就存在一些典型问题

  1. 创建一张独立的表存储计数器,表小且快
  2. 任何更新这个计数器数字的事务,这条记录上有一个全局的互斥锁,只能串行执行
  3. 可以将计数器的值保存在多行,每次随机选择一行进行加一,获取统计结果时使用聚合查询

加快 ALTER TABLE 操作速度

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

对常见的场景,能用的技巧只有两种(工作中常用方法):

  1. 一种是先在一台不提供服务的机器上执行 ALTER TABLE 操作,然后和提供服务的主库进行切换;
  2. 另一种技巧是“影子拷贝”,影子拷贝的技巧是用要求的表结构创建一张和源表无关的新表,然后通过重命名和删表操作交换两张表。
-- 影子库如下操作,工作中应有基础工具平台进行操作
DROP TABLE IF EXISTS table_name_new, table_name_old;
CREATE TABLE table_name_new LIKE table_name;
-- 加载原表 table_name 数据到 新表 table_name_new
RENAME TABLE table_name TO table_name_old, table_name_new TO table_name;
-- 可以通过一个原子的重命名操作切换影子表和原表

特殊场景,使用技巧(慎用,需要对MySQL的实现细节很了解):

  1. 基本的技术就是为想要的表结构创建一个新的.frm文件,然后用它替换已经存在的那张表的.frm文件,原理如下:
    不是所有的 ALTER TABLE 操作都会引起表重建。修改默认值为5:
    ALTER TABLE TABLE_NAME MODIFY COLUMN COLUMN_NAME TINYINT(3) NOT NULL DEFAULT 5这种方式需要拷贝整张表到一张新表;
    ALTER TABLE TABLE_NAME ALTER COLUMN COLUMN_NAME TINYINT(3) NOT NULL DEFAULT 5这种方式直接修改了.frm文件不涉及表数据拷贝,操作很快;
    理论上,MySQL 可以跳过创建新表的步骤。列的默认值实际上存在于表的.frm文件中,所以可以直接修改这个文件不需要改动表本身。
  2. 另一个常用技巧是先删除所有的非唯一索引(对唯一索引无效)、然后增加新的列(载入数据),最后重新创建删除掉的索引。原因是创建索引的工作被延迟到数据完全载入以后,这个时候已经可以通过排序来构建索引了,这样会快很多。

其他注意事项

1、表包含太多的列。MySQL的存储引擎API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列,从行缓冲中将编码过的列装换成行数据结构的操作待解非常高,转换代价依赖于列的数量。

2、避免查询时太多关联。设计表时要避免类似“EAV”类似的糟糕设计,否则可能导致后续查询时需要进行大量关联

3、小心使用枚举。注意防止过度使用枚举(ENUM),我在日常开发中直接不用。在MySQL中给枚举列增加一个新枚举值时可能就要做一次 ALTER TABLE 操作。

4、不必害怕存储NULL。避免使用NULL有好处,且建议尽可能烤炉代替方案(使用 0 、特殊值等)。当确实需要表示未知值时也不要害怕使用NULL,一味伪造(使用0、特殊值,如时间“0000-00-00 00:00:00”)可能导致很多问题

5、为了提升查询速度,经常需要建一些额外的索引,增加冗余列,甚至是创建缓存表和汇总表。这些方法会增加写查询的负担,也需要额外的维护工作,但是在设计高性能数据库时,这些都是常见的技巧:虽然写操作变慢了,但显著提高了读操作的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值