1、选择优化的数据类型:
(1)一般情况下,应该尽量选择使用可以正确存储数据的最小数据类型。
(2)尽量简单:应该用MySQL内建的类型(datetime,time等)而不是字符串来存储时间和日期;应该用整形存储IP地址。
(3)尽量避免NULL。通常情况下应该尽量指定为NOTNULL(因为可为NULL的列使得索引、索引统计和值比较都比较复杂)。如果计划在列上创建索引,就应该尽量避免NULL。
在为列选择数据类型时,第一步需要确定合适的大类型:整数、字符串、时间等。第二步选择具体的类型。
DATETIME和TIMESTAMP都可以存储相同类型的数据:时间和日期,精确到秒。然而TIMESTAMP只使用DATETIME一半的存储空间,并且会根据时区变化,具有特殊的自动更新能力,但TIMESTAMP允许的时间范围比较小。
2、整数类型
TINYINT(8),SMALLINT(16),MEDIUMINT(24), INT(32), BIGINT(64)
unsigned
int(11),数字不限制值的合法范围,只是规定交互工具中用来显示字符的个数。
3、实数类型
DECIMAL存储比BIGINT更大的整数
DECIMAL用于存储精确的小数。
FLOAT和DOUBLE类型在存储同样范围的值时,通常比DECIMAL占用更小的空间。FLOAT使用四个字节存储,DOUBLE使用8个字节存储,比FLOAT有更大的精度和更大的范围。
应该尽量在堆小数进行精确计算时才使用DECIMAL。当数据量比较大时,可以用BIGINT代替DECIMAL,将要存储的货币单位根据小数的位数乘以相应的倍数即可。
4、字符串类型。
varchar和char
varchar用于存储可变长字符串,是最常见的字符串数据类型,比定长字符串节省空间(除非指定ROW_FORMAT=FIXED)。varchar使用1个或2个额外的字节记录字符串长度,如果列的最大长度小于等于255字节,那么就使用1个字节表示,否则使用2个字节。例如:latin1字符集的情况下,varchar(10)的列需要11个字节的存储空间,而varchar(1000)则需要1002个字节。
如果字符串列的最大长度比平均长度大很多,且列的更新很少,那么使用varchar是合适的。
对于过长的Varchar,InnoDB则会把过长的varchar存储为BLOB
char类型是定长。char适合存储很短的字符串,或者所有的值都接近一个长度,对于经常变更的列,用char也比varchar更好,因为定长的var不容易产生碎片。对于非常短的列,char也比varchar在存储空间上也更加有效率。存储char类型时,mysql会删除所有的末尾空格。
Binary和varbinary用于存储二进制字符串,它们存储的是字节码而不是字符。
varchar(N)和char(N)中N指的是字符数而不是字节数
http://cau99.blog.51cto.com/1855224/383023/
TODO,研究证实之,博客之。
BLOB和TEXT是为了存储很大的数据而设计的字符串数据类型,BLOB以二进制的方式存储,TEXT以字符串的方式存储。与其他类型不同,MySQL会把每个BLOB和TEXT当做独立的对象处理,存储引擎在处理的时候会做特殊处理。当BLOB和TEXT太大时,InnoDB会专门使用外部存储区来进行存储实际数据,每个值在行内存储一个指针。
MySQL对BLOB和TEXT列进行排序与其他类型是不同的:它只对每个列的前max_sort——length字节进行排序,或者可以使用order by substring来排序。MySQL不能将BLOB和TEXT列全部长度的字符串进行索引,也不能使用这些索引消除排序。
Memory引擎不支持BLOB和TEXT类型,所以如果查询使用了BLOB和TEXT列并且需要使用隐式临时表(explain的结果中含有Usingtemporary),将不得不使用磁盘临时表,这会导致严重的性能开销。最好的方法是尽量避免使用BLOB和TEXT。
使用Enum代替常用的字符串类型。MySQL在存储枚举类型时非常紧凑,会根据列表值的数量压缩到一个或者两个字节中(MySQL内部会根据每个值保存为整数)。枚举字段是按照内部存储的整数而不是定义的字符串进行排序的。
枚举的缺点是,字符串列表是固定不变的,添加或者删除字符串需要使用ALTER TABLE,对于可能变化的列,使用Enum不是好的选择。
5、日期和时间类型
MySQL提供了两种相似的日期类型:DATETIME和TIMESTAMP,对于很多应用,它们都能工作,但也存在不同。
DATETIME能够保存大范围的值,从1001年到9999年,精度为秒,使用8个字节的存储空间。
TIMESTAMP与Unix时间戳相同,只使用4个字节的存储空间,因此它的范围比DATETIME小很多:只能表示1970到2038年(因为用4个字节表示1970年1月1日以来的秒数)
TIMESTAMP的显示依赖于时区,DATETIME是用文本格式表示的时间值。TIMESTAMP的列是NOT NULL,这与其他数据类型不一样。
除了特殊的行为之外,通常也应该尽量使用TIMESTAMP,因为它比DATETIME的空间利用率更高。如果要存储比秒更加细粒度的日期和时间可以用BIGINT或者DOUBLE。
一旦选定了某一种类型,要确保在所有关联表中同样的字段都应该使用同样的类型,混用不同类型可能会导致性能问题
6、MySQL Schema设计中的陷阱
(1)、 避免太多的列
MySQL的存储引擎API工作时通常需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容编码成各个列。从行缓冲中将编码过的列转换成行数据结构的代价是很高的。
(2)、避免过多的关联。
(3)、防止过度使用枚举。
7、范式和反范式
在范式化的数据库中,每个十四数据都会出现并且只出现一次,而在反范式化的应用中,每个事实数据会出现多次,是冗余的。
范式化的设计通常需要更多的关联。
完全的范式化设计和完全的非范式化设计通常很少使用,大部分都是混合使用。
8、物化视图:
物化视图实际上是预先计算并且存储在磁盘上的表,可以通过各种策略进行刷新和更新。MySQL并不原生支持物化视图。
9、计数器表
如果在表中保存技术器,在更新计数器时可能会碰到高并发问题:
假设有一个计数器表,只有一个字段cnt:
create table hit(
cntint unsigned not null
) ENGINE = innodb;
点击时进行更新:
updatehit set cnt = cnt+1;
那么对于任何一个想要更新这一行的事务来说,这条记录都有一个全局的互斥锁,这使得这些事务只能串行执行。要获得更高的高并发更新性能,可以将计数器保存在多个行中,每次随机选择一行进行更新,表结构如下:
create table hit(
cntint unsigned not null,
slottinyint unsigned not null primary key
) ENGINE = innodb;
然后预先在表中增加100行数据,现在选择一个随机的slot进行更新:
update hit set cnt = cnt+1 where slot =rand()*100;
然后可以这样获取总数:
select sum(*) from hit;
一个常见的需求是一天一个计数器,可以修改表的结构:
create table hit_daily(
daydate not null,
slottinyint unsigned not null,
cntint unsigned not null default 0,
primarykey(day, slot)
) ENGINE=innodb;
这种情况下可以不用向之前的例子一样预先生成行,而是on duplicate key update代替:
insert into hit_daily (day, slot,cnt)values(CURRENT_DATE, rand()*100, 1) no duplicate key update cnt = cnt+1;
如果希望减少表的行数,可以周期执行任务,将所有的结果归并到0号slot,然后删除其他的slot:
UPDATE hit_daily AS c
INNER JOIN(
SELECT day, sum(cnt) AS cnts, MIN(slot) ASmslot FROM hit_daily GROUP BY day
) AS x USING(day)
SET c.cnt=IF(c.slot=x.mslot, x.cnts, 0),
c.slot = IF(c.slot=x.mslot, 0, c.slot);
然后删除所有的非0且cnt为0 的行(避免归并之后删除之前插入了新的slot):
DELETE FROM hit_daily where slot <> 0and cnt = 0;
技巧:
1、 通过交换.frm文件加快ALTERTABLE的速度。
2、 快速创建MyIsam的索引(先禁用索引,载入数据,然后启用索引)