Java笔记 | MySQL数据库
- 数据类型
字符串类型:以 char、varchar、text 为代表,用于存储字符、字符串数据
char :定义一个固定长度的字符串,长度范围处于 1 ~ 255 之间,必须在创建表时指定。存储时如果未达到指定长度,则会使用空格填充到指定长度,查询时再将空格去掉,所以 char 类型存储的字符串末尾不能有空格。char 适合存储长度固定的数据列,例如:手机号码、身份证号等等。char 由于长度固定,不需要考虑边界问题,检索速度要快于 varchar。
varchar :定义一个可变长度的字符串,创建时指定它所允许的最大长度,最大为65535字节。
tinytext、text、mediumtext、longtext :都属于变长字符串。文本类型不能有默认值,且在创建索引时需要指定前多少个字符。
日期/时间类型:以 date、time、datetime、timestamp 为代表,用于存储日期或时间
date :用于存储日期,只能存储“年月日”,比较常见的用途是存储出生日期。
time :用于存储时间,不仅可以表示一天中的时间,也可以用于表示两个时间的时间间隔。它的取值范围是 ‘-838:59:59’ to ‘838:59:59’。除了基本的存储一天中的时间之外,time 允许以 “D HH:MM:SS” 的格式存储。其中,D 的取值是 0 ~ 34。如果要存储时间间隔,time 则会以(时间间隔 * 小时)作为小时进行存储。
datetime :日期与时间的组合格式,取值范围是‘1000-01-01
00:00:00.000000’到‘9999-12-31 23:59:59.999999’。它是最常见,用途最广的数据类型。
timestamp :用于存储日期时间数据,与 datetime 存储的数据格式是一样的,它的取值范围是:‘1970-01-01 00:00:01.000000’ UTC 到 ‘2038-01-19 03:14:07.999999’ UTC。它与 datetime 的主要区别在于时间范围要小一些。另外,timestamp 是与时区相关的,能够反映 “当前时间”。当插入时间时,会先转换为本地时区后再存储;查询时间时,会转换为本地时区后再显示。所以,不同时区的人看到的同一时间是不一样的。
数值类型:以 tinyint、int、bigint、float、double、decimal 为代表,用于存储整数或小数
INT() 括号内定义的是显示宽度。在程序中定义显示宽度之后,如果插入的值不够这个宽度,在查询时会在左边用相应宽度的空格填充。定义宽度并不会影响字段的大小和存储值的取值范围。因此在开发工作中不要把 INT (1) 当初 TINYINT 来用,也千万不要把 INT (20) 当成 BIGINT 用。
float (M, D):其中 M 定义显示长度,D 定义小数位数。但是它们是可选的,且默认值是 float (10, 2),2 是小数的位数,10 是数字的总长(包括小数)。它的小数精度可以到 24 个浮点。
double (M, D):M 和 D 的含义与 float 是相同的,默认值是 double (16, 4)。它的小数精度可以达到 53 位。
decimal:定点数据类型,保存的是精确值,通常用于精度要求非常高的计算中,在 MySQL 中它还有 1 个同义词:NUMERIC,这两个数据类型在 MySQL 中被视为相同的类型。另外,也可以利用 decimal 去保存比 bigint 还要大的整数值。底层存储方面,MySQL 将 decimal 类型的数字使用二进制字符串存储,每 4 个字节可以存储 9 个数字。假如我们定义了 decimal (D, M):则代表不包含小数点的数字总数(整数位数 + 小数位数)位数是 D,默认是 10,M 则代表小数的位数,默认是 0。由于小数点两边各有 9 个数字,所以占据 2 * 4 = 8 个字节,小数点自身占用一个字节,最终,decimal (18, 9) 一共占用 9 个字节。需要注意,如果存储的位数不够,则小数末尾会补零。但是,如果超出了声明的位数,则会报错。由于 decimal 需要比较大的空间和计算开销,它的计算效率也就没有 float 和 double 那么高,所以应该只有要求精确计算的场景下才考虑去使用 decimal。
二进制类型:以 tityblob、blob、mediumblob、longblob 为代表,用于存储二进制数据
虽然 MySQL 提供并支持大文件存储,但是这样会急剧降低数据库的性能。所以,应该谨慎使用这些数据类型,能不用的情况下尽量不用。
数据类型选择:
使用 NOT NULL,且带有 COMMENT。MySQL 在索引值为 NULL 的列时,需要额外的存储空间,所以,相对于 NOT NULL 来说,NULL 会占用更多的空间。另外,在进行比较和计算时,MySQL 要对 NULL 值做特别的处理,使用效率较低。COMMENT 用于定义列的注释信息,就好像我们在写代码一样,把重要的或者不易理解的地方,加上一些注释,方便以后查阅。
尽量使用存储需要的最小数据类型。越小的数据类型占用的磁盘、内存、CPU 缓存都会更小,存取速度也会更快。小的数据类型建立索引时所需要的空间也相对较小,这样一页中所能存储的索引节点数量也就越多,遍历时 IO 次数就会越少,索引的性能也就越好。
尽量不要存储小数,如果存储小数那么直接选择 decimal。
尽量避免使用 text 和 blob。MySQL 内存临时表并不支持 text、blob 这样的大数据类型,如果查询时包含有这样的数据,则排序操作必须使用磁盘临时表,性能会下降很多。而且对于这种数据,MySQL 还要做二次查询(因为 MySQL 实际保存的是指针,而不是真实数据),会使 SQL 性能变得很差。如果确实有需求需要使用这样的数据类型,那么在查询时一定不要直接 SELECT *,而是取出需要的列。这样 MySQL 就不会去主动查询这些数据列。最后,还需要注意,因为 MySQL 对索引长度的限制,text 类型只能用到前缀索引,并且由于存储的是指针,text 列上不能有默认值。
- SQL执行顺序
FROM-ON-JOIN-WHERE-GROUP BY-HAVING-SELECT-DISTINCT-UNION-ORDER BY-LIMIT
- 连接 (JOIN)
内连接:查询两个表都满足条件的记录。
--使用 INNER JOIN 时条件用 ON 而不是 WHERE
自然连接:保留同名列的记录
外连接:保留无关联的记录。
- DELETE、DROP、TRUNCATE
DELETE 和 TRUNCATE 只删表数据,不删表结构;DROP 会删除表结构。
DELETE 可以进行回滚;DROP 和 TRUNCATE 不可回滚。
执行速度:DROP > TRUNCATE > DELETE
#DROP删除列
ALTER TABLE mytable
DROP COLUMN col;
#DROP删除表
DROP TABLE mytable;
#DELETE删除行
DELETE FROM mytable
WHERE id = 1;
#TRUNCATE删除表
TRUNCATE TABLE mytable;
- 聚合函数
AVG():平均值
COUNT():统计行数。括号中如果是数字,和*是一样的效果。
SUM():总和。括号中如果是数字,那么结果为COUNT()结果的N倍。
MAX():最大值
MIN():最小值
注:
- 每个聚合函数接受一个参数,参数可以是数据表列,也可以是函数表达式。
- 默认情况下,聚合函数会忽略列值为 NULL 的行,不参与计算。
- 聚合函数不允许嵌套。
- 一次查询中可以出现多个聚合函数。
分组聚合:
当排序列与分组列相同时,则可以合并 GROUP BY 和 ORDER BY 子句,只需在 GROUP BY 子句的后面添加 DESC 或 ASC。
在 MySQL 中,使用 AVG、COUNT 等聚合函数对表记录进行统计操作后,可以使用 HAVING 子句对结果进行过滤,且 HAVING 子句需要写在 GROUP BY 子句之后。WHERE 子句在分组前对记录进行过滤。HAVING 子句在分组后对记录进行过滤。
- 游标
在存储过程中使用游标可以对一个结果集进行移动遍历。
游标主要用于交互式应用,其中用户需要对数据集中的任意行进行浏览和修改。
- 视图
视图是虚拟的表,是存储在数据库中的查询SQL语句,本身不包含数据,也就不能对其进行索引操作。对视图的操作和对普通表的操作一样。
视图具有如下好处:
1. 简化复杂的 SQL 操作。
2. 通过只给用户访问视图的权限,保证数据的安全性。
- 数据库事务隔离级别
读未提交 (Read Uncommitted):事务可以看到其他未提交事务的执行结果。会导致脏读、不可重复读、幻读。
读提交 (Read Committed):事务只能看见已经提交的事务所做的改变。会导致不可重复读、幻读。是Oracle的默认隔离级别
可重复读 (Repeatable Read):事务的多个实例在并发读取数据时读到同样的数据行。会导致幻读。是MySQL的默认隔离级别,InnoDB通过MVCC解决。
可串行化 (Serializable):通过强制事务排序,使之不可能相互冲突。会导致大量超时和锁竞争。
- 脏读、幻读、不可重复读
脏读:⼀个事务读到了另⼀个未提交事务修改过的数据。
不可重复读:如果⼀个事务只能读到另⼀个已经提交的事务修改过的数据,并且其他事务每对该数据进⾏⼀次修改并提交后,该事务都能查询得到最新值。
幻读:如果⼀个事务先根据某些条件查询出⼀些记录,之后另⼀个事务⼜向表中插⼊了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另⼀个事务插⼊的记录也读出来。
- 事务的四大特性 ACID
原子性 (Atomicity):事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
--回滚可以用回滚日志(Undo Log)来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
一致性 (Consistency):数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对同一个数据的读取结果都是相同的。
隔离性 (Isolation):一个事务所做的修改在最终提交以前,对其它事务是不可见的。各个事务之间不能互相干扰。
持久性 (Durability):一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。
--系统发生崩溃可以用重做日志(Redo Log)进行恢复,从而实现持久性。与回滚日志记录数据的逻辑修改不同,重做日志记录的是数据页的物理修改。
- 数据库范式
1NF:每列都是不可分割的。
2NF:在1NF的基础上,非主键列完全依赖于主键列。(消除部分函数依赖)
3NF:在2NF的基础上,非主键列只依赖于主键列。(消除主键依赖)
BCNF:在3NF的基础上,非主键列不能依赖主键的子集。
- MySQL的存储引擎
InnoDB
是 MySQL 默认的事务型存储引擎。只有在需要它不支持的特性时,才考虑使用其它存储引擎。
实现了四个标准的隔离级别,默认级别是可重复读。在可重复读隔离级别下,通过多版本并发控制(MVCC)+ Next-Key Locking 防止幻读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
MyISAM
设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。
提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。
不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
主要区别:
InnoDB支持事务,MyISAM不支持事务(也没有视图)。
InnoDB支持行级锁和表级锁,MyISAM只支持表级锁。
InnoDB支持外键,MyISAM不支持。
InnoDB 支持 MVCC(多版本并发控制,读不加锁),MyISAM 不支持。
InnoDB表必须有主键,而MyISAM可以没有主键。
InnoDB表需要更多的内存和存储,而MyISAM可被压缩,存储空间较小。
Innodb按主键大小有序插入,MyISAM按记录插入顺序保存。
InnoDB 存储引擎提供了具有提交、回滚、崩溃恢复能力的事务安全,与 MyISAM 比 InnoDB 写的效率差一些,并且会占用更多的磁盘空间以保留数据和索引。
聚簇索引:InnoDB是聚簇索引,MyISAM是非聚簇索引。
--聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。 MyISAM 是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
应用:InnoDB适合频繁修改和安全性较高的应用,MyISAM适合查询为主的应用。
- 聚簇索引
聚簇索引是一种数据存储方式。聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的就是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。这个特性决定了索引组织表中的数据也是索引的一部分。
一个表只能有一个聚簇索引。
日常工作中,根据实际情况自行添加的索引都是辅助索引,辅助索引就是一个为了找主键索引的二级索引,先找到主键索引再通过主键索引找数据。
优点:
1.数据访问更快,因为聚簇索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据比非聚簇索引更快。
2.对于主键的排序查找和范围查找速度非常快。
缺点:
1.插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键。
2.更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。
3.二级索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。
- 调优
本地环境跑⼀遍SQL,⽤explain去看⼀下执⾏计划,看看分析的结果是否符合⾃⼰的预期,⽤没⽤到相关的索引,然后再去线上环境跑⼀下看看执⾏时间(这⾥只有查询语句,修改语句也⽆法在线上执⾏)。
8.0以下版本:排除缓存干扰。
- MySQL的查询优化
避免全表扫描:
--在 WHERE、ORDER BY 涉及的列上建立索引;
--少用 IN、NOT IN,尽量用 BETWEEN 代替;
--在 WHERE 子句中尽量不使用 !=、<、>
--在 WHERE 子句中尽量不对字段进行 null 值判断,可以把 null 设为0
--在 WHERE 子句中尽量不使用 or 连接条件
--尽量使用数字字段
避免查询不需要的数据:
--在 SELECT 语句中尽量不使用 SELECT *
表连接优化:
--使用 INNER JOIN 代替 OUTER JOIN
--使用外连接代替子查询
--表连接操作中,右表用于连接的字段已建立索引,左表应尽可能小,如果有条件应该先放到左边
--使用 UNION ALL 代替 UNION
禁止排序:
--在使用 GROUP BY 时如果不关心顺序,使用 ORDER BY NULL 禁止排序。
- 数据库索引使用B+树
-- B+树的磁盘读写代价更低。B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,存储的关键字的数量也就越多,树的层级更少所以查询更快,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
-- B+树的查询效率更稳定。由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
-- B+树更便于遍历和范围查询。由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫。B+树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,缓存的命中率也会比B树高。
B树优于B+树的情况:如果经常访问的数据离根节点很近,而B树的非叶子节点本身存有关键字其数据的地址,所以这种数据检索的时候会要比B+树快。
不使用红黑树的原因:在大规模数据存储的时候,红黑树往往出现由于树的深度过大而造成磁盘IO读写过于频
繁,进而导致效率低下的情况。
使用Hash索引的缺陷: Hash 索引中经过 hash() 函数建立索引后,索引顺序与与原顺序无法保持一致,不支持范围查询;Hash 索引不支持使用索引进行排序;Hash 索引不支持模糊查询以及多列索引的最左前缀匹配;Hash 索引任何时候都必须进行回表查询;Hash 索引虽然等值查询较快,但是极其不稳定,性能不可预测,但某一键值存在大量重复时,会发生 Hash 碰撞,此时效率可能十分低下。
B+树中⼀个节点为⼀⻚或⻚的倍数最为合适。因为如果⼀个节点的⼤⼩⼩于1⻚,那么读取这个节点的时候其实也会读出1⻚,造成资源的浪费。如果⼀个节点的⼤⼩⼤于1⻚,⽐如1.2⻚,那么读取这个节点的时候会读出2⻚,也会造成资源的浪费。所以为了不造成浪费,所以最后把⼀个节点的⼤⼩控制在1⻚、2⻚、3⻚、4⻚等倍数⻚⼤⼩最为合适。
- 索引的优缺点
优点:
--索引大大减小了服务器需要扫描的数据量,将随机IO变成顺序IO。
--使用索引可以加速表与表之间的连接。
--可以显著减少查询中分组和排序的时间。
缺点:
--创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。
--索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
--当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
- 建立合适的索引
--重复率小的列建议生成索引。因为重复数据少,索引树查询更有效率。
--数据具有唯一性,建议生成唯一性索引。
--频繁 group by、order by 的列建议生成索引。可以大幅提高分组和排序效率 。
--经常用于查询条件的字段建议生成索引。通过索引查询,速度更快。
--在有外键的列上应该建立索引,加快表连接的速度。
--唯一性太差的字段不适合单独创建索引,即使该字段频繁作为查询条件。
--更新非常频繁的字段不适合创建索引。
--最左前缀匹配原则。MySQL会⼀直向右匹配直到遇到范围查询 (>,<,BETWEEN,LIKE)就停⽌匹配。
最左匹配原则:索引可以简单如⼀个列 (a),也可以复杂如多个列 (a,b,c,d),即联合索引。如果是联合索引,那么key也由多个列组成,同时,索引只能⽤于查找key是否存在(相等),遇到范围查询 (>、<、between、like左匹配)等就不能进⼀步匹配了,后续退化为线性查找。因此,列的排列顺序决定了可命中索引的列数。
--尽量选择区分度⾼的列作为索引,区分度的公式是 COUNT(DISTINCT col)/COUNT(*)。表示字段不重复的⽐率,⽐率越⼤我们扫描的记录数就越少。
--索引列不能参与计算,尽量保持列“⼲净”。⽐如, FROM_UNIXTIME(create_time)='2016-06-06'就不能使⽤索引,原因很简单,B+树中存储的都是数据表中的字段值,但是进⾏检索时,需要把所有元素都应⽤函数才能⽐较,显然这样的代价太⼤。所以语句要写成 :create_time=UNIX_TIMESTAMP('2016-06-06')。
--尽可能的扩展索引,不要新建⽴索引。⽐如表中已经有了a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
--单个多列组合索引和多个单列索引的检索查询效果不同。
--“合并索引”策略简单来讲,就是使⽤多个单列索引,然后将这些结果⽤“union或者and”来合并起来
- 锁
操作粒度:行级锁(S X)、表级锁(IS IX)、页级锁、间隙锁
共享策略:共享锁(读锁 S)、排他锁(写锁 X)、意向共享锁(IS)、意向排他锁(IX)
加锁策略:乐观锁、悲观锁
- 日志
BinLog:记录所有数据库表结构变更(例如create、alter table)以及表数据修改(insert、update、delete)的二进制日志,主从数据库同步用到的都是BinLog文件。三种模式:STATEMENT、ROW、混用
RedoLog:记录更新时,InnoDB引擎就会先把记录写到RedoLog里面,并更新内存。同时,InnoDB引擎会在空闲时将这个操作记录更新到磁盘里面。如果更新太多RedoLog处理不了的时候,需先将RedoLog部分数据写到磁盘,然后擦除RedoLog部分数据。RedoLog类似转盘。
write pos :是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。
check point:是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
两阶段提交:1 prepare阶段 --> 2 写binlog --> 3 commit
崩溃重启:
--当在2之前崩溃时,重启恢复后发现没有commit,binlog不完整,回滚。
--当在3之前崩溃时,重启恢复发现虽没有commit,但满足prepare和binlog完整,所以重启后会自动commit。
BinLog与RedoLog的区别:
1. redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。
2. redo log是物理日志,记录的是在某个数据页上做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如给ID=2这一行的c字段加1。
3. redo log是循环写的,空间固定会用完;binlog是可以追加写入的。追加写是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
UndoLog:
1. insert undo log:代表事务在 insert 新记录时产生的undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃。
2. update undo log:事务在进行 update 或 delete 时产生的 undo log;不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除。
- 条件判断函数
IF (expr, v1, v2) :如果 expr 为真(expr<>0 and expr<>NULL),则 IF 函数的返回值是 v1,否则,返回 v2。
IFNULL (v1, v2) :如果 v1 不为 NULL,则返回 v1,否则,返回 v2。
NULLIF (v1, v2) :如果 v1 等于 v2,那么返回值是 NULL,否则返回值为 v1。
CASE WHEN THEN ELSE END :CASE 语句可以让 SQL 变得简单高效,提高执行效率,且通常不会引起性能问题。
- 系统函数
VERSION () :版本
CONNECTION_ID() :当前连接id
SHOW PROCESSLIST 、SHOW FULL PROCESSLIST :查看连接信息
(USER (), CURRENT_USER (), SYSTEM_USER (), SESSION_USER ()):查看用户名、主机名,通常返回值相同。
DATABASE () 、SCHEMA () :查看当前选择的数据库
CHARSET () :字符集
- Leetcode 175 组合两个表
SELECT FirstName, LastName, City, State
FROM Person AS P LEFT OUTER JOIN Address AS A
ON P.PersonId = A.PersonId
select FirstName, LastName, City, State
from Person left join Address
on Person.PersonId = Address.PersonId
- Leetcode 181 超过经理收入的员工
SELECT A.Name AS Employee
FROM Employee AS A, Employee AS B
WHERE A.Salary>B.Salary AND A.ManagerId=B.Id
SELECT a.NAME AS Employee
FROM Employee AS a
JOIN Employee AS b
ON a.ManagerId = b.Id
AND a.Salary > b.Salary
- Leetcode 182 查找重复的电子邮箱
SELECT P1.Email
FROM Person AS P1, Person AS P2
WHERE P1.Email=P2.Email AND P1.Id!=P2.Id
GROUP BY Email
select Email from
(
select Email, count(Email) as num
from Person
group by Email
) as statistic
where num > 1
select Email
from Person
group by Email
having count(Email) > 1;