SQL Notes

窗口函数【over partition by】

窗口函数功能

1)同时具有分组和排序的功能

2)不减少原表的行数

3)语法如下:

1
2
<窗口函数> over (partition by <用于分组的列名>
order by <用于排序的列名>)

聚合函数group by与开窗函数over partition by的区别

group by分组汇总后改变表的行数,一行只有一个类别;

而over partition by分组汇总后不会减少原表中的行数。

题目

若一张表里面记录了一些用户的ID和其出行的城市信息,写一条SQL输出他出行最高频次的那条记录,表如下。

id city
10000 广州
10000 广州
10000 深圳
10001 广州
10001 深圳
10001 深圳

思路

1、先对该表出行记录汇总,计算出每个id对应出行city的次数;

1
SELECT id, city, count(*) AS ctime FROM travel GROUP BY id, city 

2、对步骤1中的出行次数进行排序;

1
2
3
SELECT t1.id, t1.city, ctime, ROW_NUMBER() over ( PARTITION BY t1.id ORDER BY ctime DESC ) AS rank FROM

(SELECT id, city, count(*) AS ctime FROM travel GROUP BY id, city ) AS t1

3、取出top 1。

1
2
3
4
5
6
7
8
9
SELECT t2.id, t2.city FROM (

SELECT t1.id, t1.city, ctime, ROW_NUMBER() over ( PARTITION BY t1.id ORDER BY ctime DESC ) AS rank FROM

(SELECT id, city, count(*) AS ctime FROM travel GROUP BY id, city ) AS t1

) AS t2

WHERE t2.rank = 1

结果

id city
10000 广州
10001 深圳

总结

关于排序

Rank():可查询出并列第一,是跳跃查询;两个第一名下来就是第三名;

Row_number():是没有重复值的排序(即使两条记录相等也是不重复的),可以利用它来实现分页;

dense_rank():连续排序,两个第二名仍然跟着第三名。

关于group by和over partition by的区别

group by分组汇总后改变表的行数,一行只有一个类别;

而over partition by分组汇总不会减少原表中的行数。

公用表表达式【WITH AS】

对于子查询,可以使用with as句法【公式表达式(CTE,Common Table Expression )】简化提高可读性,同时其效率快很多。

Redis日志级别

Redis默认的设置为verbose,开发测试阶段可以用debug,生产模式一般选用notice

1、debug:会打印出很多信息,适用于开发和测试阶段

2、verbose(冗长的):包含很多不太有用的信息,但比debug要清爽一些

3、notice:适用于生产模式

4、warning : 警告信息

Redis:默认配置文件 redis.conf

Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。普遍用于目前主流的分布式架构系统中,关于redis的详细介绍,见另一篇文章:

redis的安装与介绍blog.csdn.net

redis的多机数据库实现,主要分为以下三种:

1.Redis哨兵(Sentinel)

2.Redis复制(主从)

3.Redis集群

binlog模式分三种(row,statement,mixed)

1.Row

日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改,只记录要修改的数据,只有value,不会有sql多表关联的情况。

优点:在row模式下,bin-log中可以不记录执行的sql语句的上下文相关的信息,仅仅只需要记录那一条记录被修改了,修改成什么样了,所以row的日志内容会非常清楚的记录下每一行数据修改的细节,非常容易理解。而且不会出现某些特定情况下的存储过程和function,以及trigger的调用和触发无法被正确复制问题。

缺点:在row模式下,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容。

2.Statement

每一条会修改数据的sql都会记录到master的binlog中,slave在复制的时候sql进程会解析成和原来master端执行多相同的sql再执行。

优点:在statement模式下首先就是解决了row模式的缺点,不需要记录每一行数据的变化减少了binlog日志量,节省了I/O以及存储资源,提高性能。因为他只需要记录在master上所执行的语句的细节以及执行语句的上下文信息。

缺点:在statement模式下,由于他是记录的执行语句,所以,为了让这些语句在slave端也能正确执行,那么他还必须记录每条语句在执行的时候的一些相关信息,也就是上下文信息,以保证所有语句在slave端被执行的时候能够得到和在master端执行时候相同的结果。另外就是,由于mysql现在发展比较快,很多的新功能不断的加入,使mysql的复制遇到了不小的挑战,自然复制的时候涉及到越复杂的内容,bug也就越容易出现。在statement中,目前已经发现不少情况会造成Mysql的复制出现问题,主要是修改数据的时候使用了某些特定的函数或者功能的时候会出现,比如:sleep()函数在有些版本中就不能被正确复制,在存储过程中使用了last_insert_id()函数,可能会使slave和master上得到不一致的id等等。由于row是基于每一行来记录的变化,所以不会出现,类似的问题。

3.Mixed

从官方文档中看到,之前的 MySQL 一直都只有基于 statement 的复制模式,直到 5.1.5 版本的 MySQL 才开始支持 row 复制。从 5.0 开始,MySQL 的复制已经解决了大量老版本中出现的无法正确复制的问题。但是由于存储过程的出现,给 MySQL Replication 又带来了更大的新挑战。另外,看到官方文档说,从 5.1.8 版本开始,MySQL 提供了除 Statement 和 Row 之外的第三种复制模式:Mixed,实际上就是前两种模式的结合。在 Mixed 模式下,MySQL 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 statement 和 row 之间选择一种。新版本中的 statment 还是和以前一样,仅仅记录执行的语句。而新版本的 MySQL 中对 row 模式也被做了优化,并不是所有的修改都会以 row 模式来记录,比如遇到表结构变更的时候就会以 statement 模式来记录,如果 SQL 语句确实就是 update 或者 delete 等修改数据的语句,那么还是会记录所有行的变更。

Mysql主从复制的实现原理

MySQL之间数据复制的基础是二进制日志文件(binary log file)。一台MySQL数据库一旦启用二进制日志后,其作为master,它的数据库中所有操作都会以“事件”的方式记录在二进制日志中,其他数据库作为slave通过一个I/O线程与主服务器保持通信,并监控master的二进制日志文件的变化,如果发现master二进制日志文件发生变化,则会把变化复制到自己的中继日志中,然后slave的一个SQL线程会把相关的“事件”执行到自己的数据库中,以此实现从数据库和主数据库的一致性,也就实现了主从复制。

实现MySQL主从复制需要进行的配置:

主服务器:
开启二进制日志
配置唯一的server-id
获得master二进制日志文件名及位置
创建一个用于slave和master通信的用户账号
从服务器:
配置唯一的server-id
使用master分配的用户账号读取master二进制日志
启用slave服务

具体实现过程如下:

一、准备工作

1、主从数据库版本最好一致

2、主从数据库内数据保持一致

主数据库:182.92.172.80 /linux

从数据库:123.57.44.85 /linux

二、主数据库master修改

1.修改mysql配置

找到主数据库的配置文件my.cnf(或者my.ini),我的在/etc/mysql/my.cnf,在[mysqld]部分插入如下两行:

1
2
3
[mysqld]
log-bin=mysql-bin #开启二进制日志
server-id=1 #设置server-id

2.重启mysql,创建用于同步的用户账号

打开mysql会话shell>mysql -hlocalhost -uname -ppassword

创建用户并授权:

用户:rel1

密码:slavepass

1
2
3
mysql> CREATE USER 'repl'@'123.57.44.85' IDENTIFIED BY 'slavepass';#创建用户
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'123.57.44.85';#分配权限
mysql> flush privileges; #刷新权限

3.查看master状态,记录二进制文件名(mysql-bin.000003)和位置(73):

1
2
3
4
5
6
mysql > SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 | 73 | test | manual,mysql |
+------------------+----------+--------------+------------------+

三、从服务器slave修改

1.修改mysql配置

同样找到my.cnf配置文件,添加server-id

1
2
[mysqld]
server-id=2 #设置server-id,必须唯一

2.重启mysql,打开mysql会话,执行同步SQL语句(需要主服务器主机名,登陆凭据,二进制文件的名称和位置):

1
2
3
4
5
6
mysql> CHANGE MASTER TO
-> MASTER_HOST='182.92.172.80',
-> MASTER_USER='rep1',
-> MASTER_PASSWORD='slavepass',
-> MASTER_LOG_FILE='mysql-bin.000003',
-> MASTER_LOG_POS=73;

3.启动slave同步进程:

1
mysql> start slave;

4.查看slave状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mysql> show slave status\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 182.92.172.80
Master_User: rep1
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000013
Read_Master_Log_Pos: 11662
Relay_Log_File: mysqld-relay-bin.000022
Relay_Log_Pos: 11765
Relay_Master_Log_File: mysql-bin.000013
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
...

当Slave_IO_Running和Slave_SQL_Running都为YES的时候就表示主从同步设置成功了。接下来就可以进行一些验证了,比如在主master数据库的test数据库的一张表中插入一条数据,在slave的test库的相同数据表中查看是否有新增的数据即可验证主从复制功能是否有效,还可以关闭slave(mysql>stop slave;),然后再修改master,看slave是否也相应修改(停止slave后,master的修改不会同步到slave),就可以完成主从复制功能的验证了。

还可以用到的其他相关参数:

master开启二进制日志后默认记录所有库所有表的操作,可以通过配置来指定只记录指定的数据库甚至指定的表的操作,具体在mysql配置文件的[mysqld]可添加修改如下选项:

1
2
3
4
5
6
7
8
9
10
不同步哪些数据库  

binlog-ignore-db = mysql
binlog-ignore-db = test
binlog-ignore-db = information_schema

只同步哪些数据库,除此之外,其他不同步

binlog-do-db = game

如之前查看master状态时就可以看到只记录了test库,忽略了manual和mysql库。

Reference:

1、insert … on duplicate key update column=IF(条件,值1,值2 ) 简直神一样的操作

2、OVER(PARTITION BY)函数介绍

优化查询的一些方法

总纲:对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。

避免全表扫描的方式:

1、NULL值判断:应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:

1
select id from table where num is null;

可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:

1
select id from t where num=0;

2、应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。

3、in 和 not in 也要慎用,否则会导致全表扫描,如:

1
select id from t where num in(1,2,3);

对于连续的数值,能用 between 就不要用 in了:

1
select id from t where num between 1 and 3;

4、应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

1
2
3
4
5
6
select id from t where num=10 or num=20;

## 可以这样查询:
select id from t where num=10
union all
select id from t where num=20;

5、下面的查询也将导致全表扫描:

1
select id from t where name like '%abc%'

6、应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

1
select id from t where num/2=100   

应改为:

1
select id from t where num=100*2

7、应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。

如:

1
2
3
select id from t where substring(name,1,3)='abc';

-- name以abc开头的id

应改为:

1
select id from t where name like 'abc%';

8、不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。

9、很多时候用 exists 代替 in 是一个好的选择:

1
select num from a where num in(select num from b)  

用下面的语句替换:

1
select num from a where exists(select 1 from b where num=a.num)   

10、索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率, 因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。

一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

11、尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

12、尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

13、任何地方都不要使用select * from t,用具体的字段列表代替“*”,不要返回用不到的任何字段。

14、避免频繁创建和删除临时表,以减少系统表资源的消耗。

15、临时表并不是不可使用,适当地使用它们可以使某些例程更有效。例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。

16、在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log,以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。

17、如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

18、尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。

19、使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。

20、与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。

21、尽量避免大事务操作,提高系统并发能力。

22、尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理

参考:sql优化的几种方式_freedom-CSDN博客_sql优化

1、为搜索字段建索引,index

1
2
3
4
5
6
7
8
9
10
11
12
### CREATE INDEX 语法
CREATE INDEX index_name
ON table_name (column_name)

-- create index test_index ON purchase_log_table(OrderNumber);

### CREATE UNIQUE INDEX 语法
CREATE UNIQUE INDEX index_name
ON table_name (column_name)

### drop index
drop index test_index on purchase_log_table

2、用固定长度的表执行会更快。不知道长度的情况下,varchar省空间,但是char查询效率高。

3、“NULL” columns requires additional space in the row whether their values are null.

“NULL”列在行中需要额外的空间,无论它们的值是否为空。

4、explain select 查询

5、MYISAM适用于大量查询的应用,但对于有写操作的就不很很有好,,整表锁,执行完才有其他操作

它不支持事务,也不支持外键,尤其是访问速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用基本都可以使用这个引擎来创建表。

6、InnoDB存储引擎提供了具有提交、回滚和崩溃恢复能力的事务安全。但是对比MyISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以保留数据和索引。

MEMORY

memory使用存在内存中的内容来创建表。每个MEMORY表实际对应一个磁盘文件,格式是.frm。MEMORY类型的表访问非常快,因为它到数据是放在内存中的,并且默认使用HASH索引,但是一旦服务器关闭,表中的数据就会丢失,但表还会继续存在。

MERGE

merge存储引擎是一组MyISAM表的组合,这些MyISAM表结构必须完全相同,MERGE表中并没有数据,对MERGE类型的表可以进行查询、更新、删除的操作,这些操作实际上是对内部的MyISAM表进行操作。

------ 本文结束------
Donate comment here.

欢迎关注我的其它发布渠道