--创建数据库 CREATE DATABASE [IF NOT EXISTS] db_name [[DEFAULT] CHARACTER SET [=] charset_name] [[DEFAULT] COLLATE [=] collation_name] --删除数据库 DROP DATABASE [IF EXISTS] db_name --备份数据库 --opt Same as --add-drop-table, --add-locks, --create-options, -- --quick, --extended-insert, --lock-tables, --set-charset, -- and --disable-keys. Enabled by default, disable with -- --skip-opt. --master-data 用于bin-log增量备份 --no-data / -d 备份结构 --no-create-info / -t 备份数据 --数据库备份中还有好多参数 可以在terminal下执行mysqldump --help查看 --备份多个库 mysqldump -hPup --opt --master-data=2 -B database1 database2 ... > "D:/mysqlDump/mydb.sql" --备份多个表 mysqldump -hPup --opt --master-data=2 database [tables1] [tables2] ... > "D:/mysqlDump/mydb.sql" --还原数据库 mysql -hPup < "D:/mysqlDump/mydb.sql" source "D:/mysqlDump/mydb.sql" -- 创建表 -- http://dev.mysql.com/doc/refman/5.7/en/create-table.html CREATE TABLE [IF NOT EXISTS] tbl_name (create_definition,...) --从已有表中创建表结构 CREATE TABLE [IF NOT EXISTS] tbl_name { LIKE old_tbl_name | (LIKE old_tbl_name) } --删除表 DROP TABLE [IF EXISTS] tbl_name [, tbl_name, ...] --表定义 ALTER TABLE tbl_name [alter_specification [, alter_specification] ... --添加字段 ALTER TABLE tbl_name ADD [COLUMN] col_name column_definition [FIRST | AFTER col_name ] --修改字段名称 ALTER TABLE tbl_name CHANGE [COLUMN] old_col_name new_col_name column_definition [FIRST|AFTER col_name] --修改字段类型 ALTER TABLE tbl_name MODIFY [COLUMN] col_name column_definition [FIRST | AFTER col_name] --创建主键索引 ALTER TABLE tbl_name ADD PRIMARY KEY(col_name) --创建唯一索引 ALTER TABLE tbl_name ADD UNIQUE(col_name) --创建普通索引 ALTER TABLE tbl_name ADD INDEX(col_name) --创建全文索引 ALTER TABLE tbl_name ADD FULLTEXT(col_name) --创建多列索引 ALTER TABLE tbl_name ADD INDEX(col_name1,col_name2,....) --删除列 ALTER TABLE tbl_name DROP [COLUMN] col_name --删除主键索引 ALTER TABLE tbl_name DROP PRIMARY KEY --删除普通索引 ALTER TABLE tbl_name DROP {INDEX|KEY} index_name --注:索引是不可更改的,想更改必须删除重新建。 --创建视图 CREATE VIEW view_name [(column_list)] AS select_statement 例: mysql> CREATE TABLE t (qty INT, price INT); mysql> INSERT INTO t VALUES(3, 50); mysql> CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t; mysql> SELECT * FROM v; +------+-------+-------+ | qty | price | value | +------+-------+-------+ | 3 | 50 | 150 | mysql> CREATE VIEW v (mycol) AS SELECT 'abc'; Query OK, 0 rows affected (0.01 sec) mysql> SELECT "mycol" FROM v; +-------+ | mycol | +-------+ | abc | +-------+ 1 row in set (0.00 sec --删除视图 DROP VIEW view_name; --几个简单的基本的sql语句 选择: select * from table1 where 范围 插入: insert into table1(field1,field2) values(value1,value2) 删除: delete from table1 where 范围 更新: update table1 set field1=value1 where 范围 查找: select * from table1 where field1 like '%value1%' ---like的语法很精妙,查资料! 排序: select * from table1 order by field1,field2 [desc] 总数: select count as totalcount from table1 求和: select sum(field1) as sumvalue from table1 平均: select avg(field1) as avgvalue from table1 最大: select max(field1) as maxvalue from table1 最小: select min(field1) as minvalue from table1 --几个高级查询运算词 A: UNION 运算符 UNION 运算符通过组合其他两个结果表(例如 TABLE1 和 TABLE2)并消去表中任何重复行而派生出一个结果表. 当 ALL 随 UNION 一起使用时(即 UNION ALL),不消除重复行.两种情况下,派生表的每一行不是来自 TABLE1 就是来自 TABLE2 B: EXCEPT 运算符 EXCEPT 运算符通过包括所有在 TABLE1 中但不在 TABLE2 中的行并消除所有重复行而派生出一个结果表. 当 ALL 随 EXCEPT 一起使用时 (EXCEPT ALL),不消除重复行. C: INTERSECT 运算符 INTERSECT 运算符通过只包括 TABLE1 和 TABLE2 中都有的行并消除所有重复行而派生出一个结果表. 当 ALL随 INTERSECT 一起使用时 (INTERSECT ALL),不消除重复行. 注: 使用运算词的几个查询结果行必须是一致的. --使用外连接 A: left join 左外连接(左连接): 结果集几包括连接表的匹配行,也包括左连接表的所有行. SQL: select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c B: right join 右外连接(右连接): 结果集既包括连接表的匹配连接行,也包括右连接表的所有行. C: full/cross join 全外连接: 不仅包括符号连接表的匹配行,还包括两个连接表中的所有记录. --分组: Group by 一张表,一旦分组 完成后,查询后只能得到组相关的信息. 组相关的信息: (统计信息) count,sum,max,min,avg 分组的标准) 在SQLServer中分组时:不能以text,ntext,image类型的字段作为分组依据 在selecte统计函数中的字段,不能和普通的字段放在一起 --复制数据 INSERT [INTO] tbl_name [(col_name,...)] SELECT [(col_name,...)] from other_tbl_name --例: INSERT INTO b (a, b, c) SELECT d,e,f FROM b; --例子: 1.显示文章 提交人和最后回复时间 select a.title,a.username,b.adddate from table a,(select max(adddate) adddate from table where table.title=a.title) b 2.外连接查询(表名1:a 表名2:b) select a.a, a.b, a.c, b.c, b.d, b.f from a LEFT OUT JOIN b ON a.a = b.c 3.在线视图查询(表名1:a) select * from (SELECT a,b,c FROM a) T where t.a > 1 4.between的用法,between限制查询数据范围时包括了边界值,not between不包括 select * from table1 where time between time1 and time2 select a,b,c, from table1 where a not between 数值1 and 数值2 5.in 的使用方法(将in中匹配最多的列放在最前面) select * from table1 where a [not] in ('值1','值2','值4','值6') 6.两张关联表,删除主表中已经在副表中没有的信息 delete from table1 where not exists ( select * from table2 where table1.field1=table2.field1 ) 7.四表联查问题 select * from a left inner join b on a.a=b.b right inner join c on a.a=c.c inner join d on a.a=d.d where ..... 8.日程安排提前五分钟提醒 select * from 日程安排 where datediff('minute',开始时间,getdate())>5 9.一条sql 语句搞定数据库分页(sqlSer) select top 10 b.* from (select top 20 主键字段,排序字段 from 表名 order by 排序字段 desc) a,表名 b where b.主键字段 = a.主键字段 order by a.排序字段 10.前10条记录(sqlSer) select top 10 * form table1 where 范围 11.选择在每一组b值相同的数据中对应的a最大的记录的所有信息(类似这样的用法可以用于论坛每月排行榜,每月热销产品分析,按科目成绩排名,等等.) select a,b,c from tablename ta where a=(select max(a) from tablename tb where tb.b=ta.b) 12.包括所有在 TableA中但不在 TableB和TableC中的行并消除所有重复行而派生出一个结果表 (select a from tableA ) except (select a from tableB) except (select a from tableC) 13.随机取出10条数据(sqlSer) select top 10 * from tablename order by newid() 14.删除重复记录 delete from tablename where id not in (select max(id) from tablename group by col1,col2,...) 15.按姓氏笔画排序 Select * From TableName Order By CustomerName Collate Chinese_PRC_Stroke_ci_as //从少到多 16.开头到N条记录 Select Top N * From 表 17.N到M条记录(要有主索引ID) Select Top M-N * From 表 Where ID in (Select Top M ID From 表) Order by ID Desc 18.N到结尾记录 Select Top N * From 表 Order by ID Desc 19.一张表有一万多条记录,表的第一个字段 RecID 是自增长字段,写一个SQL语句,找出表的第31到第40个记录. select top 10 recid from A where recid not in(select top 30 recid from A) --技巧 1=1,1=2的使用,在SQL语句组合时用的较多 "where 1=1" 是表示选择全部 "where 1=2" 全部不选 SQL 语句性能优化策略(参考)
1、对查询进行优化,应尽量避免全表扫描,首先应考虑在where及order by涉及的列上建立索引。
2、应尽量避免在where子句中对字段进行null值判断,创建表时NULL是默认值,但大多数时候应该使用NOT NULL,或者使用一个特殊的值,如0,-1作为默认值。
3、应尽量避免在where子句中使用!=或<>操作符,MySQL只有对以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE。
4、应尽量避免在where子句中使用or来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,可以使用UNION合并查询:select id from t where num=10 union all select id from t where num=20。
5、in和not in也要慎用,否则会导致全表扫描,对于连续的数值,能用between就不要用in了:Select id from t where num between 1 and 3。
6、下面的查询也将导致全表扫描:select id from t where name like‘%abc%’或者select id from t where name like‘%abc’若要提高效率,可以考虑全文检索。而select id from t where name like‘abc%’才用到索引。
7、如果在where子句中使用参数,也会导致全表扫描。
8、应尽量避免在where子句中对字段进行表达式操作,应尽量避免在where子句中对字段进行函数操作。
9、很多时候用exists代替in是一个好的选择:select num from a where num in(select num from b)。用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)。
10、索引固然可以提高相应的select的效率,但同时也降低了insert及update的效率,因为insert或update时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
11、应尽可能的避免更新clustered索引数据列, 因为clustered索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新clustered索引数据列,那么需要考虑是否应将该索引建为clustered索引。
12、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
13、尽可能的使用varchar/nvarchar代替char/nchar,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
14、最好不要使用 * 返回所有:select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
15、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
16、使用表的别名(Alias):当在SQL语句中连接多个表时,请使用表的别名并把别名前缀于每个Column上。这样一来,就可以减少解析的时间并减少那些由Column歧义引起的语法错误。
17、使用“临时表”暂存中间结果 :
简化SQL语句的重要方法就是采用临时表暂存中间结果,但是临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在tempdb中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。
18、一些SQL查询语句应加上nolock,读、写是会相互阻塞的,为了提高并发性能,对于一些查询,可以加上nolock,这样读的时候可以允许写,但缺点是可能读到未提交的脏数据。
使用nolock有3条原则:
-
查询的结果用于“插、删、改”的不能加nolock;
-
查询的表属于频繁发生页分裂的,慎用nolock ;
-
使用临时表一样可以保存“数据前影”,起到类似Oracle的undo表空间的功能,能采用临时表提高并发性能的,不要用nolock。
19、不要有超过5个以上的表连接(JOIN),考虑使用临时表或表变量存放中间结果。少用子查询,视图嵌套不要过深,一般视图嵌套不要超过2个为宜。
20、将需要查询的结果预先计算好放在表中,查询的时候再Select。这在SQL7.0以前是最重要的手段,例如费用的计算。
21、用OR的字句可以分解成多个查询,并且通过UNION 连接多个查询。他们的速度只同是否使用索引有关,如果查询需要用到联合索引,用UNION all执行的效率更高。多个OR的字句没有用到索引,改写成UNION的形式再试图与索引匹配。一个关键的问题是否用到索引。
22、在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数。
23、当服务器的内存够多时,配制线程数量 = 最大连接数+5,这样能发挥最大的效率;否则使用 配制线程数量<最大连接数启用SQL SERVER的线程池来解决,如果还是数量 = 最大连接数+5,严重的损害服务器的性能。
24、尽量使用exists代替select count(1)来判断是否存在记录,count函数只有在统计表中所有行数时使用,而且count(1)比count(*)更有效率。
25、尽量使用“>=”,不要使用“>”。
26、索引的使用规范:
-
索引的创建要与应用结合考虑,建议大的OLTP表不要超过6个索引;
-
尽可能的使用索引字段作为查询条件,尤其是聚簇索引,必要时可以通过index index_name来强制指定索引;
-
避免对大表查询时进行table scan,必要时考虑新建索引;
-
在使用索引字段作为条件时,如果该索引是联合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用;
-
要注意索引的维护,周期性重建索引,重新编译存储过程。
27、下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:
SELECT * FROM record WHERE substrINg(card_no,1,4)=’5378’ (13秒)
SELECT * FROM record WHERE amount/30< 1000 (11秒)
SELECT * FROM record WHERE convert(char(10),date,112)=’19991201’ (10秒)
分析:
WHERE子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引。
如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:
SELECT * FROM record WHERE card_no like ‘5378%’ (< 1秒)
SELECT * FROM record WHERE amount< 1000*30 (< 1秒)
SELECT * FROM record WHERE date= ‘1999/12/01’ (< 1秒)
28、当有一批处理的插入或更新时,用批量插入或批量更新,绝不会一条条记录的去更新。
29、SQL语句用大写,因为Oracle总是先解析SQL语句,把小写的字母转换成大写的再执行。
30、别名的使用,别名是大型数据库的应用技巧,就是表名、列名在查询中以一个字母为别名,查询速度要比建连接表快1.5倍。
31、避免死锁,在你的存储过程和触发器中访问同一个表时总是以相同的顺序;事务应经可能地缩短,在一个事务中应尽可能减少涉及到的数据量;永远不要在事务中等待用户输入。
32、索引创建规则:
-
表的主键、外键必须有索引;
-
数据量超过300的表应该有索引;
-
经常与其他表进行连接的表,在连接字段上应该建立索引;
-
经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
-
索引应该建在选择性高的字段上;
-
索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
-
复合索引的建立需要进行仔细分析,尽量考虑用单字段索引代替;
-
正确选择复合索引中的主列字段,一般是选择性较好的字段;
-
复合索引的几个字段是否经常同时以AND方式出现在Where子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;
-
如果复合索引中包含的字段经常单独出现在Where子句中,则分解为多个单字段索引;
-
如果复合索引所包含的字段超过3个,那么仔细考虑其必要性,考虑减少复合的字段;
-
如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;
-
频繁进行数据操作的表,不要建立太多的索引;
-
删除无用的索引,避免对执行计划造成负面影响;
-
表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。
-
尽量不要对数据库中某个含有大量重复的值的字段建立索引。
33、MySQL查询优化总结:
使用慢查询日志去发现慢查询,使用执行计划去判断查询是否正常运行,总是去测试你的查询看看是否他们运行在最佳状态下。
久而久之性能总会变化,避免在整个表上使用count(*),它可能锁住整张表,使查询保持一致以便后续相似的查询可以使用查询缓存,在适当的情形下使用GROUP BY而不是DISTINCT,在WHERE、GROUP BY和ORDER BY子句中使用有索引的列,保持索引简单,不在多个索引中包含同一个列。
有时候MySQL会使用错误的索引,对于这种情况使用USE INDEX,检查使用SQL_MODE=STRICT的问题,对于记录数小于5的索引字段,在UNION的时候使用LIMIT不是是用OR。
为了避免在更新前SELECT,使用INSERT ON DUPLICATE KEY或者INSERT IGNORE,不要用UPDATE去实现,不要使用MAX,使用索引字段和ORDER BY子句,LIMIT M,N实际上可以减缓查询在某些情况下,有节制地使用,在WHERE子句中使用UNION代替子查询,在重新启动的MySQL,记得来温暖你的数据库,以确保数据在内存和查询速度快,考虑持久连接,而不是多个连接,以减少开销。
34、MySQL备份过程:
-
从二级复制服务器上进行备份;
-
在进行备份期间停止复制,以避免在数据依赖和外键约束上出现不一致;
-
彻底停止MySQL,从数据库文件进行备份;
-
如果使用MySQL dump进行备份,请同时备份二进制日志文件 – 确保复制没有中断;
-
不要信任LVM快照,这很可能产生数据不一致,将来会给你带来麻烦;
-
为了更容易进行单表恢复,以表为单位导出数据——如果数据是与其他表隔离的。
-
当使用mysqldump时请使用–opt;
-
在备份之前检查和优化表;
-
为了更快的进行导入,在导入时临时禁用外键约束。;
-
为了更快的进行导入,在导入时临时禁用唯一性检测;
-
在每一次备份后计算数据库,表以及索引的尺寸,以便更够监控数据尺寸的增长;
-
通过自动调度脚本监控复制实例的错误和延迟;
-
定期执行备份。
35、查询缓冲并不自动处理空格,因此,在写SQL语句时,应尽量减少空格的使用,尤其是在SQL首和尾的空格(因为查询缓冲并不自动截取首尾空格)。
36、我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。
37、EXPLAIN SELECT查询用来跟踪查看效果:
使用EXPLAIN关键字可以让你知道MySQL是如何处理你的SQL语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。EXPLAIN的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的。
使用explain分析SQL执行计划
1、type
-
system:表仅有一行,基本用不到;
-
const:表最多一行数据配合,主键查询时触发较多;
-
eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型;
-
ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取;
-
range:只检索给定范围的行,使用一个索引来选择行。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range;
-
index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小;
-
all:全表扫描;
-
性能排名:system > const > eq_ref > ref > range > index > all。
-
实际sql优化中,最后达到ref或range级别。
2、Extra常用关键字
-
Using index:只从索引树中获取信息,而不需要回表查询;
-
这里补充一点覆盖索引的内容,覆盖索引(Covering Index),或者叫索引覆盖, 也就是平时所说的不需要回表操作,准确的来说,他并不是指某一种具体的索引,而是select的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select列表中的字段,而不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
是否是覆盖索引可通过使用explain,根据输出的extra列来判断,如果使用了覆盖索引显示为using index。
前缀索引因为不能判断索引列的值对应的完整数据是否符合要求,所以一定会回表,也就不能享受覆盖索引带来的好处
-
是否是覆盖索引可通过使用explain,根据输出的extra列来判断,如果使用了覆盖索引显示为using index。
前缀索引因为不能判断索引列的值对应的完整数据是否符合要求,所以一定会回表,也就不能享受覆盖索引带来的好处。
-
Using where:WHERE子句用于限制哪一个行匹配下一个表或发送到客户。除非你专门从表中索取或检查所有行,如果Extra值不为Using where并且表联接类型为ALL或index,查询可能会有一些错误。需要回表查询。
-
Using temporary:mysql常建一个临时表来容纳结果,典型情况如查询包含可以按不同情况列出列的
GROUP BY
和ORDER BY
子句时;
执行explain select * from table;
38、当只要一行数据时使用LIMIT 1 :
39、选择表合适存储引擎:
-
myisam:应用时以读和插入操作为主,只有少量的更新和删除,并且对事务的完整性,并发性要求不是很高的。
-
InnoDB:事务处理,以及并发条件下要求数据的一致性。除了插入和查询外,包括很多的更新和删除。(InnoDB有效地降低删除和更新导致的锁定)。
对于支持事务的InnoDB类型的表来说,影响速度的主要原因是AUTOCOMMIT默认设置是打开的,而且程序没有显式调用BEGIN 开始事务,导致每插入一条都自动提交,严重影响了速度。可以在执行SQL前调用begin,多条SQL形成一个事物(即使autocommit打开也可以),将大大提高性能。
40、优化表的数据类型,选择合适的数据类型:
原则:更小通常更好,简单就好,所有字段都得有默认值,尽量避免null。
MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。
因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。
例如:在定义邮政编码这个字段时,如果将其设置为CHAR(255),显然给数据库增加了不必要的空间。甚至使用VARCHAR这种类型也是多余的,因为CHAR(6)就可以很好的完成任务了。
数据库查询优化参考:鸣谢此文档专属作者,这里我只做了归纳
1、为什么数据库会慢?
无论是关系型数据库还是NoSQL,任何存储系统决定于其查询性能的主要因素包括:
(1)查找时间复杂度
(2)数据总量
(3)高负载
而决定于查找时间复杂度主要有两个因素:
-
查找算法
-
存储数据结构
无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。
从关系型数据库角度出发,索引结构基本固定是B+Tree,时间复杂度是O(log n),存储结构是行式存储。因此咱们对于关系数据库能优化的一般只有数据量。
而高负载造成原因有高并发请求、复杂查询等,导致CPU、磁盘繁忙等,而服务器资源不足则会导致慢查询等问题。该类型问题一般会选择集群、数据冗余的方式分担压力。
应该站在哪个层面思考优化?
从上图可见,自顶向下的一共有四层,分别是硬件、存储系统、存储结构、具体实现。层与层之间是紧密联系的,每一层的上层是该层的载体。因此越往顶层越能决定性能的上限,同时优化的成本也相对会比较高,性价比也随之越低。
以最底层的具体实现为例,那么索引的优化的成本应该是最小的,可以说加了索引后无论是CPU消耗还是响应时间都是立竿见影降低;
然而一个简单的语句,无论如何优化加索引也是有局限的,当在具体实现这层没有任何优化空间的时候就得往上一层【存储结构】思考,思考是否从物理表设计的层面出发优化(如分库分表、压缩数据量等),如果是文档型数据库得思考下文档聚合的结果;如果在存储结构这层优化得没效果,得继续往再上一次进行考虑,是否关系型数据库应该不适合用在现在得业务场景?如果要换存储,那么得换怎样得NoSQL?
数据库优化方案的核心本质有三种:减少数据量、用空间换性能、选择合适的存储系统
(1)减少数据量:数据归档、中间表生成、数据序列化存储、分库分表
就如上面所说的,无论是哪种存储,数据量越少,自然查询性能就越高,随着数据量增多,资源的消耗(CPU、磁盘读写繁忙)、耗时也会越来越高。
目前市面上的NoSQL基本上都支持分片存储,所以其天然分布式写的能力从数据量上能得到非常的解决方案。而关系型数据库,查找算法与存储结构是可以优化的空间比较少,因此咱们一般思考出发点只有从如何减少数据量的这个角度进行选择优化,因此本类型的优化方案主要针对关系型数据库进行处理。
数据归档:
注意点:别一次性迁移数量过多,建议低频率多次限量迁移。像MySQL由于删除数据后是不会释放空间的,可以执行命令OPTIMIZE TABLE释放存储空间,但是会锁表,如果存储空间还满足,可以不执行。
建议优先考虑该方案,主要通过数据库作业把非热点数据迁移到历史表,如果需要查历史数据,可新增业务入口路由到对应的历史表(库)。
优化前:order表
orderID | userID | Money | dateTime |
1 | 125 | 106.3 | 2023-5-28 |
2 | 125 | 98.3 | 2022-3-10 |
3 | 125 | 65.2 | 2021-10-1 |
优化后:order表
orderID | userID | Money | dateTime |
1 | 125 | 106.3 | 2023-5-28 |
order_history(三个月前的数据)
orderID | userID | Money | dateTime |
2 | 125 | 98.3 | 2022-3-10 |
3 | 125 | 65.2 | 2021-10-1 |
中间表生成:
中间表(结果表)其实就是利用调度任务把复杂查询的结果跑出来存储到一张额外的物理表,因为这张物理表存放的是通过跑批汇总后的数据,因此可以理解成根据原有的业务进行了高度的数据压缩
那么数据的压缩比率是否越低越好?下面有一段口诀:
-
字段越多,粒度越细,灵活性越高,可以以中间表进行不同业务联表处理。
-
字段越少,粒度越粗,灵活性越低,一般作为结果表查询出来。
数据序列化存储:
user_buy_chapter表
RecordID | userID | chapterID | money | dateTime |
1 | 103 | 1 | 233 | 2023-05-10 |
2 | 103 | 2 | 366 | 2023-05-20 |
RecordID | userID | chapterInfo |
1 | 103 | [{chapterID:1,money:233,dateTime:2023-05-10},{chapterID:2,money:366,dateTime:2023-05-20}}] |
在数据库以序列化存储的方式,对于一些不需要结构化存储的业务来说是一种很好减少数据量的方式,特别是对于一些M*N
的数据量的业务场景,如果以M作为主表优化,那么就可以把数据量维持最多是M的量级。另外像订单的地址信息,这种业务一般是不需要根据里面的字段检索出来,也比较适合。
分库分表:
分库分表是一种优化成本很大的方案。几个建议:
-
分库分表是实在没有办法的办法,应放到最后选择。
-
优先选择NoSQL代替,因为NoSQL诞生基本上为了扩展性与高性能。
-
究竟分库还是分表?量大则分表,并发高则分库
-
不考虑扩容,一部做到位。因为技术更新太快了,每3-5年一大变。
拆分方式:垂直拆分、水平拆分
垂直拆分更多是从业务角度进行拆分 ,主要是为了降低业务耦合度;此外以SQL Server为例,一页是8KB存储,如果在一张表里字段越多,一行数据自然占的空间就越大,那么一页数据所存储的行数就自然越少,那么每次查询所需要IO则越高因此性能自然也越慢;因此反之,减少字段也能很好提高性能
userID | NickName | Phone | point | vipLv |
1 | 张华 | 1332255xxxx | 100 | 2 |
2 | 刘烨 | 1386936xxxx | 200 | 3 |
user表:
userID | NickName | Phone |
1 | 张华 | 1332255xxxx |
2 | 刘烨 | 1386936xxxx |
userInfo表:
userID | point | vipLv |
1 | 100 | 2 |
2 | 200 | 3 |
水平拆分更多是从技术角度进行拆分 ,拆分后每张表的结构是一模一样的,简而言之就是把原有一张表的数据,通过技术手段进行分片到多张表存储,从根本上解决了数据量的问题。
userID | NickName | Phone | point | vipLv |
1 | 张华 | 1332255xxxx | 100 | 2 |
2 | 刘烨 | 1386936xxxx | 200 | 3 |
user_1表:
userID | NickName | Phone | point | vipLv |
1 | 张华 | 1332255xxxx | 100 | 2 |
user_2表:
userID | NickName | Phone | point | vipLv |
2 | 刘烨 | 1386936xxxx | 200 | 3 |
路由方式水平拆分:
进行水平拆分后,根据分区键(sharding key)原来应该在同一张表的数据拆解写到不同的物理表里,那么查询也得根据分区键进行定位到对应的物理表从而把数据给查询出来。
路由方式一般有三种拆分:区间范围拆分、Hash拆分、分片映射表拆分。每种路由方式都有自己的优点和缺点,可以根据对应的业务场景进行选择。
区间范围拆分:根据某个元素的区间的进行拆分,以时间为例子,假如有个业务我们希望以月为单位拆分那么表就会拆分像 table_2022-04
,
Hash拆分:也是一种常用的路由方式,根据Hash算法取模以数据量均匀分别存储在物理表里,缺点是对于带分区键的查询依赖特别强,如果不带分区键就无法定位到具体的物理表导致相关所有表都查询一次,而且在分库的情况下对于Join、聚合计算、分页等一些RDBMS的特性功能还无法使用。
order表:
orderID | userID | money | dateTime |
1 | 105 | 233 | 2023-05-10 |
2 | 201 | 5698 | 2023-05-10 |
3 | 116 | 8963 | 2023-05-10 |
Hash(userid)%2(hsah取模)
order_user1表:
orderID | userID | money | dateTime |
1 | 105 | 233 | 2023-05-10 |
3 | 116 | 8963 | 2023-05-10 |
order_user2表:
orderID | userID | money | dateTime |
2 | 201 | 5698 | 2023-05-10 |
一般分区键就一个,假如有时候业务场景得用不是分区键的字段进行查询,那么难道就必须得全部扫描一遍?其实可以使用分片映射表的方式,简单来说就是额外有一张表记录额外字段与分区键的映射关系。
举个例子,有张订单表,原本是以UserID作为分区键拆分的,现在希望用OrderID进行查询,那么得有额外得一张物理表记录了OrderID与UserID的映射关系。因此得先查询一次映射表拿到分区键,再根据分区键的值路由到对应的物理表查询出来。
可能有些朋友会问,那这映射表是否多一个映射关系就多一张表,还是多个映射关系在同一张表。我优先建议单独处理,如果说映射表字段过多,那跟不进行水平拆分时的状态其实就是一致的
用空间换时间:
该类型的两个方案都是用来应对高负载的场景,方案有以下两种:分布式缓存、一主多从。
与其说这个方案叫用空间换性能,我认为用空间换资源更加贴切一些。因此两个方案的本质主要通数据冗余、集群等方式分担负载压力。
对于关系型数据库而言,因为他的ACID特性让它天生不支持写的分布式存储,但是它依然天然的支持分布式读。
缓存层级可以分好几种:客户端缓存、API服务本地缓存和分布式缓存 ,咱们这次只聊分布式缓存。一般我们选择分布式缓存系统都会优先选择NoSQL的键值型数据库,例如Memcached、Redis,如今Redis的数据结构多样性,高性能,易扩展性也逐渐占据了分布式缓存的主导地位。
避免滥用缓存:
缓存应该是按需使用,从28法则来看,80%的性能问题由主要的20%的功能引起。滥用缓存的后果会导致维护成本增大,而且有一些数据一致性的问题也不好定位。
特别像一些动态条件的查询或者分页,key的组装是多样化的,量大又不好用keys指令去处理,当然我们可以用额外的一个key把记录数据的key以集合方式存储,删除时候做两次查询,先查Key的集合,然后再遍历Key集合把对应的内容删除。这一顿操作下来无疑是非常废功夫的,谁弄谁知道。
避免缓存击穿:
当缓存没有数据,就得跑去数据库查询出来,这就是缓存穿透。假如某个时间临界点数据是空的例如周排行榜,穿透过去的无论查找多少次数据库仍然是空,而且该查询消耗CPU相对比较高,并发一进来因为缺少了缓存层的对高并发的应对,这个时候就会因为并发导致数据库资源消耗过高,这就是缓存击穿。数据库资源消耗过高就会导致其他查询超时等问题。
该问题的解决方案也简单,对于查询到数据库的空结果也缓存起来,但是给一个相对快过期的时间。有些同行可能又会问,这样不就会造成了数据不一致了么?一般有数据同步的方案像分布式缓存、后续会说的一主多从、CQRS,只要存在数据同步这几个字,那就意味着会存在数据一致性的问题,因此如果使用上述方案,对应的业务场景应允许容忍一定的数据不一致。
一主多从:
常用的分担数据库压力还有一种常用做法,就是读写分离、一主多从 。咱们都是知道关系型数据库天生是不具备分布式分片存储的,也就是不支持分布式写,但是它天然的支持分布式读。
一主多从是部署多台从库只读实例,通过冗余主库的数据来分担读请求的压力,路由算法可有代码实现或者中间件解决,具体可以根据团队的运维能力与代码组件支持视情况选择。
一主多从在还没找到根治方案前是一个非常好的应急解决方案,特别是在现在云服务的年代,扩展从库是一件非常方便的事情,而且一般情况只需要运维或者DBA解决就行,无需开发人员接入。
当然这方案也有缺点,因为数据无法分片,所以主从的数据量完全冗余过去,也会导致高的硬件成本。从库也有其上限,从库过多了会主库的多线程同步数据的压力。
选择合适的存数系统:
NoSQL主要以下五种类型:键值型、文档型、列型、图型、搜素引擎 ,不同的存储系统直接决定了查找算法、存储数据结构,也应对了需要解决的不同的业务场景。NoSQL的出现也解决了关系型数据库之前面临的难题(性能、高并发、扩展性等)。
数据同步方式:
一般讨论到数据同步的方式主要是分推和拉:
-
推指的是由数据变更端通过直接或者间接的方式把数据变更的记录发送到接收端,从而进行数据的一致性处理,这种主动的方式优点是实时性高。
-
拉指的是接收端定时的轮询数据库检查是否有数据需要进行同步,这种被动的方式从实现角度来看比推简单,因为推是需要数据变更端支持变更日志的推送的。
替换(选择)存储系统:
因为从本质来看该模式与CQRS的核心本质是一样的,主要是要对NoSQL的优缺点有一个全面认识,这样才能在对应业务场景选择与判断出一个合适的存储系统。这里我像大家介绍一本书马丁.福勒《NoSQL精粹》,这本书我重复看了好几遍,也很好全面介绍各种NoSQL优缺点和使用场景。
当然替换存储的时候,我这里也有个建议:加入一个中间版本,该版本做好数据同步与业务开关,数据同步要保证全量与增加的处理,随时可以重来,业务开关主要是为了后续版本的更新做的一个临时型的功能,主要避免后续版本更新不顺利或者因为版本更新时导致的数据不一致的情况出现。在跑了一段时间后,验证了两个不同的存储系统数据是一致的后,接下来就可以把数据访问层的底层调用替换了。如此一来就可以平滑的更新切换。
结束
在这里再次提醒一句,每个方案都有属于它的应对场景,咱们只能根据业务场景选择对应的解决方案,大部分都存在数据同步的情况,只要存在数据同步,无论是一主多从、分布式缓存、CQRS都好,都会有数据一致性的问题导致,因此这些方案更多适合一些只读的业务场景。当然有些写后既查的场景,可以通过过渡页或者广告页通过用户点击关闭切换页面的方式来缓解数据不一致性的情况。