Mysql数据库设计

目录

数据库设计

设计工具

三大范式

设计规范

字段类型

表的操作

表的创建

左右连接

count函数

删除

表与表的关系

数据库的优化



数据库设计

设计工具

  • CHINER元数建模:http://chiner-release.httpchk.com/CHINER-win_v3.5.7.rar
  • Excel文档表结构设计生成建表语句函数:=""&B2&""&" "&C2&" "&IF(F2="Y","NULL","NOT NULL")&IF(D2="Y"," "&"PRIMARY KEY ","")&"COMMENT="""&E2&""","
  • vba工具

三大范式

设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。目前数据库有六种范式:第一范式、第二范式、第三范式、巴斯-科德范式、第四范式、第五范式(又称完美范式)。

 第一范式:列不可再分,具有原子性,即数据库表的每一列都是不可分割的原子数据项,不能是集合、数组等非原子数据项。

 第二范式:在满足第一范式的前提下,每一列都依赖于主键,表中的每一个字段都依赖于主键。

 第三范式:在满足第二范式的前提下,每一列都直接依赖于主键,而不能通过其他列来间接的依赖主键。 三大范式只是一般设计数据库的基本理念,可以建立冗余较小、结构合理的数据库。如果有特殊情况,当然要特殊对待,数据库设计最重要的是看需求跟性能,需求>性能>表结构。所以不能一味的去追求范式建立数据库。

设计规范

推荐utf-8字符集、固定字符串的列尽可能多用定长char少用varchar、所有的InnoDB表都设计一个无业务用途的自增列做主键、字段长度满足需求前提下尽可能选择长度小的、字段属性尽量都加NOT NULL约束(空的字段不能走索引,查询速度慢)对于某些文本字段,例如“省份”或者“性别”我们可以将他们定义为ENUM类型、尽可能不使用TEXT/BLOB类型,确实需要的话,建议拆分到子表中,不要和主表放在一起,避免SELECT的时候读性能太差、读取数据时,只选取所需要的列,不要每次都SELECT * 尤其是读到一些TEXT/BLOB类型,确实需要的话,建议拆分到子表中,不要和主表放在一起,避免SELECT的时候读性能太差、对一个VARCHAR(N)列创建索引时,通常取其50%(甚至更小)左右长度创建前缀索引就足以满足80%以上的查询需求了,没必要创建整列的全长度索引、多用复合索引,少用多个独立索引。

字段类型

字段类型相关表字段是用来存储数据内容大小的,其大小的设置跟存储到磁盘分配的磁盘块多少有直接关系。

换算单位:1KB=1024B,1B=8bit(位);
数据存储是以二进制存储的,一个子节占8位,
例:十进制数 9大小占一个子节,用二进制表示为:0000 1001。
查看数据库表结构文件存放地址执行:show global variables like "%datadir%"。

char(n) :固定长度类型,char(10),当你输入"abc"三个字符的时候,它们占的空间还是10个字节,其他7个是空字节。其优点是存取数据效率高,缺点是占用空间大,以空间换时间,适用场景:存储密码的md5值,固定长度的,使用char非常合适。

varchar(n) :可变长度,存储的值是每个值占用的字节再加上一个用来记录其长度的字节的长度。从空间上考虑varcahr比较合适;从效率上考虑char比较合适。

float(m,d):单精度浮点数, m显示长度,d表示小数点位数,占用空间4个字节。例如:float(4,2) 表示的范围是-99.99~99.99。

decimal(m,d):m显示长度,d表示小数点位数。例如:decimal(5,2) 表示范围:-999.99 ~ 999.99。往数据库字段类型为float(10.8)、decimal(10,8)分别插入数据10.12345678,10.12345678,然后执行select * from 表,会发现float类型的字段显示数据为10.12345695,而decimal字段显示的数据为10.12345678,float单精度小数位数小于decimal,decimal如果不指定精度,默认为(10,0)。

double:浮点类型有两种,分别是单精度浮点数(float)和双精度浮点数(double),定点类型只有一种,就是decimal,可以存储16位的十进制数,占空间8字节

表的操作

表的创建

创建表:
    CREATE TABLE `user` (
      `id` INT AUTO_INCREMENT,
      `name` VARCHAR (20),
      PRIMARY KEY (`id`)
    );
UPDATE:
  UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
INSERT: 
  INSERT INTO 表名称 VALUES (值1, 值2,....)
  INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)
DELETE:
  DELETE FROM 表名称 WHERE 列名称 = 值
修改表结构:
    ALTER TABLE table_name add column_name datatype
    ALTER TABLE table_name drop COLUMN column_name
    ALTER TABLE table_name modify COLUMN column_name datatype

左右连接

INNER JOIN(内连接,或等值连接):取得两个表中存在连接匹配关系的记录。

  select * from A inner join B on A.name = B.name;

LEFT JOIN(左连接):取得左表(table1)完全记录,即是右表(table2)并无对应匹配记录。

  select * from A left join B on A.name = B.name;
  select * from A left join B on A.name=B.name where A.id is null or B.id is null;

RIGHT JOIN(右连接):与LEFT JOIN 相反,取得右表(table2)完全记录,即是左表(table1)并无匹配对应记录。drop、truncate和delete的区别

count函数

按照性能排序:count(*)=count(1)>count(主键字段)>count(字段)

count()是一个聚合函数,函数的参数可以是一个字段名,也可以是其他任意表达式,其作用是统计符合查询条件的记录中,函数指定的参数不为null的记录有多少个。

-----name不是索引字段------
select count(name) from user;

这条语句是统计user表中,name字段不为null的记录有多少个。如果有一条记录的name字段值为null是不会被统计进去的。

Mysql数据库设计_第1张图片

 这个查询会采用全表扫描的方式来计数,所以它的执行效率是比较差的。

select count(1) from user;

这条语句是统计user表中,1这个表达式不null的记录有多少个,1是一个单纯的数字永远都不会为null,所以上面这条语句其实就是统计user表中有多少条记录。

执行流程:如果表里只有主键索引没有二级索引,InnoDB循环遍历聚簇索引,将读取到的记录返回给server层,但是不会读取记录中任何字段的值,因为count函数的参数是1 不是字段永远不会为null,server层每从InnoDB读取到一条记录,就将count变量加1。count(1)比count(主键)少一个步骤,就是不需要读取记录中字段的值,所以count(1)比count(主键)性能要高一些。

select (主键字段) from user;

上面语句执行过程:mysql server层会维护名叫count的变量。server层会循环向InnoDB读取一条记录,如果count函数指定的参数不为null,变量count加1,直到符合查询条件的记录被读完,不count变量值返回给客户端。索引分为聚簇索引和二级索引,区别在于聚簇索引的叶子节点存放的是实际数据,二级索引的叶子节点存放的是主键值而不是实际数据。

如果表里只有主键索引没有二级索引,InnoDB会循环遍历聚簇索引,将读取到的记录返回给server层,然后读取记录中的id值是否为null,不为null就加1。 如果表里面存在二级索引,sql优化器会先选择遍历二级索引。

select count(*) from user;

当使用count(*)时 mysql会将*转化为参数0来处理,count(*)等同于count(0),所以count(*)跟count(1)执行过程基本一样,性能没什么差异。

删除

DELETE 对数据一行一行的删除,将每行的删除操作记录在日志中保存,以便进行回滚操作。

TRUNCATE 一次性地从表中删除所有的数据不记录操作日志,数据不能恢复,删除的过程中不会激活与表有关的删除触发器。执行速度快。

DROP 语句将表所占用的空间全释放掉。TRUNCATE和DELETE只删除数据, DROP则删除整个表(结构和数据)。

表与表的关系

 一对一

 一对多

 多对多

数据库的优化

硬件层

1.CPU(运算):48核CPU。
2.内存:96G-256G,跑3-4个实例。
3.disk(磁盘IO):机械盘:选SAS,数量越多越好。性能:SSD(高并发)>SAS(普通业务线上)>SATA(线下)
选SSD:使用SSD或者PCIe SSD设备,可提升上千倍的IOPS效率。随机IO:SAS单盘能力300IOPS 
SSD随机IO:单盘能力可达35000IOPS Flashcache HBA卡 
4.raid磁盘阵列:4快盘:RAID0>RAID1(推荐)>RAID5(少用)>RAID1。主库选择raid10,
从库可选raid5/raid0/raid10,从库配置等于或大于主库
5.网卡:使用多块网卡bond,以及buffer,tcp优化。千兆网卡及千兆、万兆交换机
数据库属于IO密集型服务,硬件尽量不要使用虚拟化

操作系统层面优化

1.选择x86_64系统,推荐使用CentOS6.8 linux,关闭NUMA特性
2.将操作系统和数据分开(逻辑上和物理上)
3.避免使用Swap交换分区
4.避免使用软件磁盘阵列
5.避免使用LVM逻辑卷
6.删除服务器上未使用的安装包和守护进程

网络优化

#优化系统套接字缓冲区
#Increase TCP max buffer size
net.core.rmem_max=16777216 #最大socket读buffer
net.core.wmem_max=16777216 #最大socket写buffer
net.core.wmem_default = 8388608 #该文件指定了接收套接字缓冲区大小的缺省值(以字节为单位)
net.core.rmem_default = 8388608
#优化TCP接收/发送缓冲区

# Increase Linux autotuning TCPbuffer limits
net.ipv4.tcp_rmem=4096 87380 16777216
net.ipv4.tcp_wmem=4096 65536 16777216
net.ipv4.tcp_mem = 94500000 915000000927000000
#优化网络设备接收队列
net.core.netdev_max_backlog=3000

数据库层面优化

 1.my.cnf参数优化 此优化主要针对innodb引擎,default-storage-engine=Innodb,

innodb_buffer_pool_size:指定用于索引的缓冲区大小
innodb_file_per_table = 1:使用独立表空间
innodb_data_file_path = ibdata1:1G:autoextend,不要使用默认的10%
innodb_log_file_size=256M,设置innodb_log_files_in_group=2,基本可满足90%以上的场景;
innodb_log_file_size
long_query_time = 1记录那些执行较慢的SQL,用于后续的分析排查;
max_connection(最大连接数)max_connection_error(最大错误数,建议设置为10万以上,而open_files_limit、innodb_open_files、table_open_cache、table_definition_cache这几个参             数则可设为约10倍于max_connection的大小;)不要设置太大,会将数据库撑爆 
tmp_table_size = 64M :设置内存临时表最大值。如果超过该值,则会将临时表写入磁盘,其范围1KB到4GB。
max_heap_table_size = 64M:独立的内存表所允许的最大容量。
table_cache = 614:给经常访问的表分配的内存,物理内存越大,设置就越大。调大这个值,一般情况下可以降低磁盘IO,但相应的会占用更多的内存,这里设置为614。

2.sql语句的优化

(1)查询SQL尽量不要使用select *,用具体字段可以节省资源、减少网络开销。可能用到覆盖索引,减少回表,提高查询效率。

//正例
SELECT * FROM user

//反例
SELECT id,username,tel FROM user

(2)避免在where子句中使用or来连接条件。用or可能会使索引失效,从而全表扫描。对于or没有索引的salary这种情况,假设它走了id的索引,但是走到salary查询条件时,它还得全表扫描。也就是说整个过程需要三步:全表扫描+索引扫描+合并。如果它一开始就走全表扫描,直接一遍扫描就搞定。虽然mysql是有优化器的,出于效率与成本考虑,遇到or条件,索引还是可能失效的。

----反例
SELECT * FROM user WHERE id=1 OR salary=5000

----正例 使用union all
SELECT * FROM user WHERE id=1 
UNION ALL
SELECT * FROM user WHERE salary=5000

----或者分开两条sql
SELECT * FROM user WHERE id=1
SELECT * FROM user WHERE salary=5000

(3)尽量使用数值替代字符串类型。因为引擎在处理查询和连接时会逐个比较字符串中每一个字符;而对于数字型而言只需要比较一次就够了;字符会降低查询和连接的性能,并会增加存储开销;

主键(id):primary key优先使用数值类型inttinyint。

性别(sex):0代表女,1代表男;数据库没有布尔类型,mysql推荐使用tinyint。

(4)使用varchar代替char。varchar变长字段按数据内容实际长度存储,存储空间小,可以节省存储空间;char按声明大小存储,不足补空格;其次对于查询来说,在一个相对较小的字段内搜索,效率更高;char的长度是固定的,而varchar2的长度是可以变化的。比如,存储字符串“101”,对于char(10),表示你存储的字符将占10个字节(包括7个空字符),在数据库中它是以空格占位的,而同样的varchar2(10)则只占用3个字节的长度,10只是最大值,当你存储的字符小于10时,按实际长度存储。

//反例
`address` char(100) DEFAULT NULL COMMENT '地址'

//正例
`address` varchar(100) DEFAULT NULL COMMENT '地址'

(5)where中使用默认值代替nul。并不是说使用了is null或者 is not null就会不走索引了,这个跟mysql版本以及查询成本都有关;如果mysql优化器发现,走索引比不走索引成本还要高,就会放弃索引,这些条件 !=,<>,is null,is not null经常被认为让索引失效;其实是因为一般情况下,查询的成本高,优化器自动放弃索引的;如果把null值,换成默认值,很多时候让走索引成为可能,同时,表达意思也相对清晰一点;

----反例
SELECT * FROM user WHERE age IS NOT NULL

----正例
SELECT * FROM user WHERE age>0

 (6)避免在where子句中使用!=或<>操作符。使用!=<>很可能会让索引失效,应尽量避免在where子句中使用!=<>操作符,否则引擎将放弃使用索引而进行全表扫描,实现业务优先,实在没办法,就只能使用,并不是不能使用。

----反例
SELECT * FROM user WHERE salary!=5000
SELECT * FROM user WHERE salary<>5000

(7)inner join 、left join、right join,优先使用inner join。三种连接如果结果相同,优先使用inner join,如果使用left join左边表尽量小。inner join 内连接,只保留两张表中完全匹配的结果集;left join会返回左表所有的行,即使在右表中没有匹配的记录;right join会返回右表所有的行,即使在左表中没有匹配的记录;如果inner join是等值连接,返回的行数比较少,所以性能相对会好一点;使用了左连接,左边表数据结果尽量小,条件尽量放到左边处理,意味着返回的行数可能比较少;这是mysql优化原则,就是小表驱动大表,小的数据集驱动大的数据集,从而让性能更优;

(8)提高group by语句的效率

-----反例,先分组,再过滤
select job, avg(salary) from employee 
group by job
having job ='develop' or job = 'test';

------正例,先过滤,后分组
select job,avg(salary) from employee 
where job ='develop' or job = 'test' 
group by job;

-----在执行到该语句前,把不需要的记录过滤掉

(9)清空表时优先使用truncate

truncate table在功能上与不带 where子句的 delete语句相同:二者均删除表中的全部行。但 truncate table比 delete速度快,且使用的系统和事务日志资源少。

delete语句每次删除一行,并在事务日志中为所删除的每行记录一项。truncate table通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。

truncate table删除表中的所有行,但表结构及其列、约束、索引等保持不变。新行标识所用的计数值重置为该列的种子。如果想保留标识计数值,请改用 DELETE。如果要删除表定义及其数据,请使用 drop table语句。

对于由 foreign key约束引用的表,不能使用 truncate table,而应使用不带  where子句的 DELETE 语句。由于 truncate table不记录在日志中,所以它不能激活触发器。

truncate table不能用于参与了索引视图的表。

(10)操作delete或者update语句,加个limit或者循环分批次删除。

降低写错SQL的代价

清空表数据可不是小事情,如果加limit,删错也只是丢失部分数据,可以通过binlog日志快速恢复的。SQL效率很可能更高,SQL中加了limit 1,如果第一条就命中目标return, 没有limit的话,还会继续执行扫描表。避免长事务,delete执行时,如果age加了索引,MySQL会将所有相关的行加写锁和间隙锁,所有执行相关行会被锁住,如果删除数量大,会直接影响相关业务无法使用。数据量大的话,容易把CPU打满,如果你删除数据量很大时,不加 limit限制一下记录数,容易把cpu打满,导致越删越慢。锁表,一次性删除太多数据,可能造成锁表,会有lock wait timeout exceed的错误,所以建议分批操作。

(11)UNION操作符

UNION在进行表链接后会筛选掉重复的记录,所以在表链接后会对所产生的结果集进行排序运算,删除重复的记录再返回结果。实际大部分应用中是不会产生重复的记录,最常见的是过程表与历史表UNION。

select username,tel from user
union
select departmentname from department

SQL在运行时先取出两个表的结果,再用排序空间进行排序删除重复的记录,最后返回结果集,如果表数据量大的话可能会导致用磁盘进行排序。推荐方案:采用UNION ALL操作符替代UNION,因为UNION ALL操作只是简单的将两个结果合并后就返回。

(12)批量插入性能提升

-----多条提交
INSERT INTO user (id,username) VALUES(1,'哪吒编程');
INSERT INTO user (id,username) VALUES(2,'妲己');

-----批量提交
INSERT INTO user (id,username) VALUES(1,'哪吒编程'),(2,'妲己');

(13)表连接不宜太多,索引不宜太多,一般5个以内。关联的表个数越多,编译的时间和开销也就越大,每次关联内存中都生成一个临时表,应该把连接表拆开成较小的几个执行,可读性更高,如果一定需要连接很多表才能得到数据,那么意味着这是个糟糕的设计了,阿里规范中,建议多表联查三张表以下,索引不宜太多,一般5个以内,索引并不是越多越好,虽其提高了查询的效率,但却会降低插入和更新的效率;索引可以理解为一个就是一张表,其可以存储数据,其数据就要占空间;索引表的数据是排序的,排序也是要花时间的;insertupdate时有可能会重建索引,如果数据量巨大,重建将进行记录的重新排序,所以建索引需要慎重考虑,视具体情况来定;一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否有存在的必要。

(14)避免在索引列上使用内置函数

-----反例
SELECT * FROM user WHERE DATE_ADD(birthday,INTERVAL 7 DAY) >=NOW();

-----正例
SELECT * FROM user WHERE  birthday >= DATE_ADD(NOW(),INTERVAL 7 DAY);

------使用索引列上内置函数,索引失效。

(15)组合索引

排序时应按照组合索引中各列的顺序进行排序,即使索引中只有一个列是要排序的,否则排序性能会比较差。

create index IDX_USERNAME_TEL on user(deptid,position,createtime);
select username,tel from user where deptid= 1 and position = 'java开发' order by deptid,position,createtime desc; 

实际上只是查询出符合 deptid= 1 and position = 'java开发'条件的记录并按createtime降序排序,但写成order by createtime desc性能较差。

(16)复合索引最左特性

-----创建复合索引
ALTER TABLE employee ADD INDEX idx_name_salary (name,salary)

-----满足复合索引的最左特性,哪怕只是部分,复合索引生效
SELECT * FROM employee WHERE NAME='哪吒编程'

------没有出现左边的字段,则不满足最左特性,索引失效
SELECT * FROM employee WHERE salary=5000

------复合索引全使用,按左侧顺序出现 name,salary,索引生效
SELECT * FROM employee WHERE NAME='哪吒编程' AND salary=5000

------虽然违背了最左特性,但MySQL执行SQL时会进行优化,底层进行颠倒优化
SELECT * FROM employee WHERE salary=5000 AND NAME='哪吒编程'

复合索引也称为联合索引,当我们创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。联合索引不满足最左原则,索引一般会失效。

(17)优化like语句

模糊查询,程序员最喜欢的就是使用like,但是like很可能让你的索引失效。

-----反例
select * from citys where name like '%大连' (不使用索引)
select * from citys where name like '%大连%' (不使用索引)

-----正例
select * from citys where name like '大连%' (使用索引) 。

(18)使用explain分析你SQL执行计划。

type

  1. system:表仅有一行,基本用不到;

  2. const:表最多一行数据配合,主键查询时触发较多;

  3. eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型;

  4. ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取;

  5. range:只检索给定范围的行,使用一个索引来选择行。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range;

  6. index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小;

  7. all:全表扫描;

  8. 性能排名:system > const > eq_ref > ref > range > index > all。

  9. 实际sql优化中,最后达到ref或range级别。

(19)其它优化方式

设计表的时候,所有表和字段都添加相应的注释。SQL书写格式,关键字大小保持一致,使用缩进。修改或删除重要数据前,要先备份。用exists代替in是一个好的选择。where后面的字段,留意其数据类型的隐式转换。

未使用索引

SELECT * FROM user WHERE NAME=110

因为不加单引号时,是字符串跟数字的比较,它们类型不匹配; MySQL会做隐式的类型转换,把它们转换为数值类型再做比较;

尽量把所有列定义为NOT NULL,NOT NULL列更节省空间,NULL列需要一个额外字节作为判断是否为 NULL的标志位。NULL列需要注意空指针问题,NULL列在计算和比较的时候,需要注意空指针问题。伪删除设计,数据库和表的字符集尽量统一使用UTF8,可以避免乱码问题;可以避免,不同字符集比较转换,导致的索引失效问题;select count(*) from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是一定要杜绝的。避免在where中对字段进行表达式操作SQL解析时,如果字段相关的是表达式就进行全表扫描 ;字段干净无表达式,索引生效;关于临时表,避免频繁创建和删除临时表,以减少系统表资源的消耗;在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert;如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除。先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定;索引不适合建在有大量重复数据的字段上,比如性别,排序字段应创建索引去重distinct过滤字段要少,带distinct的语句占用cpu时间高于不带distinct的语句,当查询很多字段时,如果使用distinct,数据库引擎就会对数据进行比较,过滤掉重复数据,然而这个比较、过滤的过程会占用系统资源,如cpu时间,尽量避免大事务操作,提高系统并发能力,所有表必须使用Innodb存储引擎。Innodb「支持事务,支持行级锁,更好的恢复性」,高并发下性能更好,所以呢,没有特殊要求(即Innodb无法满足的功能如:列存储,存储空间数据等)的情况下,所有表必须使用Innodb存储引擎。尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

3.索引的优化: DBA参与,抓出慢SQL,减少上线后的慢SQL数据。

配置my.cnf long_query_time = 2 log-slow-queries=/data/3306/slow-log.log log_queries_not_using_indexs 按天轮询:slow-log.log

慢查询的日志分析工具——mysqlsla或pt-query-digest(推荐) pt-quey-diges,mysqldumpslow,mysqlsla,myprofi,mysql-explain-slow-log,mysqllogfileter 3)

每天晚上0点定时分析慢查询,发到核心开发,DBA分析,及高级运维,CTO的邮箱里 DBA分析给出优化建议-->核心开发确认更新-->DBA线上操作处理

定期使用pt-duplicate-key-checker检查并删除重复的索引

定期使用pt-index-usage工具检查并删除使用频率很低的索引

使用pt-online-schema-change来完成大表的ONLINE DDL需求

有时候MySQL会使用错误的索引,对于这种情况使用USE INDEX

使用explain及set profile优化SQL语句

你可能感兴趣的:(数据库,数据库,sql,database)