乐趣区

关于java:少侠如何写一手好-SQL

博主(编码砖家)负责的我的项目次要采纳阿里云数据库 MySQL,最近频繁呈现慢 SQL 告警,执行时 间最长的竟 然高达 5 分钟 。导出日志后剖析,次要起因居然是 没有命中索引和没有分页解决

其实这是十分低级的谬误,我不禁后背一凉,团队成员的技术水平亟待进步啊。革新这些 SQL 的过程中,总结了一些教训分享给大家,如果有谬误欢送批评指正。

MySQL 性能

最大数据量

抛开数据量和并发数,谈性能都是耍流氓。MySQL 没有限度单表最大记录数,它取决于操作系统对文件大小的限度。

文件系统 单文件大小限度
FAT32 最大 4G
NTFS 最大 64GB
NTFS5.0 最大 2TB
EXT2 块大小为 1024 字节,文件最大容量 16GB;块大小为 4096 字节,文件最大容量 2TB
EXT3 块大小为 4KB,文件最大容量为 4TB
EXT4 实践能够大于 16TB

《阿里巴巴 Java 开发手册》提出单表行数超过 500 万行或者单表容量超过 2GB,才举荐分库分表。性能由综合因素决定,抛开业务复杂度,影响水平顺次是硬件配置、MySQL 配置、数据表设计、索引优化。500 万这个值仅供参考,并非铁律。博主已经操作过超过 4 亿行数据的单表,分页查问最新的 20 条记录耗时 0.6 秒,SQL 语句大抵是 select field_1,field_2 from table where id < #{prePageMinId} order by id desc limit 20,prePageMinId 是上一页数据记录的最小 ID。尽管过后查问速度还对付,随着数据一直增长,有朝一日必然不堪重负。分库分表是个周期长而危险高的大活儿,应该尽可能在以后构造上优化,比方降级硬件、迁徙历史数据等等,切实没辙了再分。对分库分表感兴趣的同学能够浏览分库分表的根本思维。

最大并发数

并发数是指同一时刻数据库能解决多少个申请,由 max_connections 和 max_user_connections 决定。**max_connections 是指 MySQL 实例的最大连接数,上限值是 16384,max_user_connections 是指每个数据库用户的最大连接数。MySQL 会为每个连贯提供缓冲区,意味着耗费更多的内存。如果连接数设置太高硬件吃不消,太低又不能充分利用硬件。个别要求两者比值超过 10%,计算方法如下:

max_used_connections / max_connections * 100% = 3/100 *100% ≈ 3%

查看最大连接数与响应最大连接数:

show variables like '%max_connections%';show variables like '%max_user_connections%';

在配置文件 my.cnf 中批改最大连接数

[mysqld]max_connections = 100max_used_connections = 20

查问耗时 0.5 秒

倡议将单次查问耗时管制在 0.5 秒以内,0.5 秒是个经验值,源于用户体验的  3 秒准则。如果用户的操作 3 秒内没有响应,将会腻烦甚至退出。响应工夫 = 客户端 UI 渲染耗时 + 网络申请耗时 + 利用程序处理耗时 + 查询数据库耗时,0.5 秒就是留给数据库 1 / 6 的解决工夫。

施行准则

相比 NoSQL 数据库,MySQL 是个娇气软弱的家伙。它就像体育课上的女同学,一点纠纷就和同学闹别扭 (扩容难),跑两步就气喘吁吁(容量小并发低),经常身材不适要销假(SQL 束缚太多)。现在大家都会搞点分布式,应用程序扩容比数据库要容易得多,所以施行准则是  数据库少干活,应用程序多干活

  • 充分利用但不滥用索引,须知索引也耗费磁盘和 CPU。
  • 不举荐应用数据库函数格式化数据,交给利用程序处理。
  • 不举荐应用外键束缚,用应用程序保证数据准确性。
  • 写多读少的场景,不举荐应用惟一索引,用应用程序保障唯一性。
  • 适当冗余字段,尝试创立两头表,用应用程序计算两头后果,用空间换工夫。
  • 不容许执行极度耗时的事务,配合应用程序拆分成更小的事务。
  • 预估重要数据表(比方订单表)的负载和数据增长态势,提前优化。

数据表设计

数据类型

数据类型的抉择准则:更简略或者占用空间更小。

  • 如果长度可能满足,整型尽量应用 tinyint、smallint、medium\_int 而非 int。
  • 如果字符串长度确定,采纳 char 类型。
  • 如果 varchar 可能满足,不采纳 text 类型。
  • 精度要求较高的应用 decimal 类型,也能够应用 BIGINT,比方准确两位小数就乘以 100 后保留。

尽量采纳 timestamp 而非 datetime。

类型 占据字节 形容
datetime 8 字节 ‘1000-01-01 00:00:00.000000’ to ‘9999-12-31 23:59:59.999999
timestamp 4 字节 ‘1970-01-01 00:00:01.000000’ to ‘2038-01-19 03:14:07.999999’

相比 datetime,timestamp 占用更少的空间,以 UTC 的格局贮存主动转换时区。

防止空值

MySQL 中字段为 NULL 时仍然占用空间,会使索引、索引统计更加简单。从 NULL 值更新到非 NULL 无奈做到原地更新,容易产生索引决裂影响性能。尽可能将 NULL 值用有意义的值代替,也能防止 SQL 语句外面蕴含 is not null的判断。

text 类型优化

因为 text 字段贮存大量数据,表容量会很早涨下来,影响其余字段的查问性能。倡议抽取进去放在子表里,用业务主键关联。

索引优化

索引分类

  1. 一般索引:最根本的索引。
  2. 组合索引:多个字段上建设的索引,可能减速复合查问条件的检索。
  3. 惟一索引:与一般索引相似,但索引列的值必须惟一,容许有空值。
  4. 组合惟一索引:列值的组合必须惟一。
  5. 主键索引:非凡的惟一索引,用于惟一标识数据表中的某一条记录,不容许有空值,个别用 primary key 束缚。
  6. 全文索引:用于海量文本的查问,MySQL5.6 之后的 InnoDB 和 MyISAM 均反对全文索引。因为查问精度以及扩展性不佳,更多的企业抉择 Elasticsearch。

索引优化

  1. 分页查问很重要,如果查问数据量超过 30%,MYSQL 不会应用索引。
  2. 单表索引数不超过 5 个、单个索引字段数不超过 5 个。
  3. 字符串可应用前缀索引,前缀长度管制在 5 - 8 个字符。
  4. 字段唯一性太低,减少索引没有意义,如:是否删除、性别。
  5. 正当应用笼罩索引,如下所示:

    select login_name, nick_name from member where login\_name = ?

login_name, nick_name 两个字段建设组合索引,比 login\_name 简略索引要更快

SQL 优化

分批解决

博主小时候看到鱼塘挖开小口子放水,水面有各种漂浮物。浮萍和树叶总能顺利通过出水口,而树枝会挡住其余物体通过,有时还会卡住,须要人工清理。MySQL 就是鱼塘,最大并发数和网络带宽就是出水口,用户 SQL 就是漂浮物。不带分页参数的查问或者影响大量数据的 update 和 delete 操作,都是树枝,咱们要把它打散分批解决,举例说明:业务形容:更新用户所有已过期的优惠券为不可用状态。SQL 语句:update status=0 FROMcoupon WHERE expire_date <= #{currentDate} and status=1;如果大量优惠券须要更新为不可用状态,执行这条 SQL 可能会堵死其余 SQL,分批解决伪代码如下:

int pageNo = 1;
int PAGE_SIZE = 100;
while(true) {List<Integer> batchIdList = queryList('select id FROM `coupon` WHERE expire_date <= #{currentDate} and status = 1 limit #{(pageNo-1) * PAGE_SIZE},#{PAGE_SIZE}');
    if (CollectionUtils.isEmpty(batchIdList)) {return;}
    update('update status = 0 FROM `coupon` where status = 1 and id in #{batchIdList}')
    pageNo ++;
}

操作符 <> 优化

通常 <> 操作符无奈应用索引,举例如下,查问金额不为 100 元的订单:select id from orders where amount != 100;如果金额为 100 的订单极少,这种数据分布重大不均的状况下,有可能应用索引。鉴于这种不确定性,采纳 union 聚合搜寻后果,改写办法如下:

(select id from orders where amount > 100) union all(select id from orders where amount < 100 and amount > 0)

OR 优化

在 Innodb 引擎下 or 无奈应用组合索引,比方:

select id,product_name from orders where mobile_no = '13421800407' or user_id = 100;

OR 无奈命中 mobile_no + user_id 的组合索引,可采纳 union,如下所示:

(select id,product_name from orders where mobile_no = '13421800407') union(select id,product_name from orders where user_id = 100);

此时 id 和 product\_name 字段都有索引,查问才最高效。

IN 优化

  1. IN 适宜主表大子表小,EXIST 适宜主表小子表大。因为查问优化器的一直降级,很多场景这两者性能差不多一样了。
  2. 尝试改为 join 查问,举例如下:

select id from orders where user\_id in (select id from user where level = ‘VIP’);

采纳 JOIN 如下所示:

select o.id from orders o left join user u on o.user_id = u.id where u.level = 'VIP';

不做列运算

通常在查问条件列运算会导致索引生效,如下所示:查问当日订单

select id from order where date_format(create_time,'%Y-%m-%d') = '2019-07-01';

date\_format 函数会导致这个查问无奈应用索引,改写后:

select id from order where create_time between '2019-07-01 00:00:00' and '2019-07-01 23:59:59';

防止 Select all

如果不查问表中所有的列,防止应用 SELECT *,它会进行全表扫描,不能无效利用索引。

Like 优化

like 用于含糊查问,举个例子(field 已建设索引):

SELECT column FROM table WHERE field like '%keyword%';

这个查问未命中索引,换成上面的写法:

SELECT column FROM table WHERE field like 'keyword%';

去除了后面的 % 查问将会命中索引,然而产品经理肯定要前后含糊匹配呢?全文索引 fulltext 能够尝试一下,但 Elasticsearch 才是终极武器。

Join 优化

join 的实现是采纳 Nested Loop Join 算法,就是通过驱动表的后果集作为根底数据,通过该结数据作为过滤条件到下一个表中循环查问数据,而后合并后果。如果有多个 join,则将后面的后果集作为循环数据,再次到后一个表中查问数据。

  1. 驱动表和被驱动表尽可能减少查问条件,满足 ON 的条件而少用 Where,用小后果集驱动大后果集。
  2. 被驱动表的 join 字段上加上索引,无奈建设索引的时候,设置足够的 Join Buffer Size。
  3. 禁止 join 连贯三个以上的表,尝试减少冗余字段。

Limit 优化

limit 用于分页查问时越往后翻性能越差,解决的准则:放大扫描范畴,如下所示:

select * from orders order by id desc limit 100000,10 耗时 0.4 秒 select * from orders order by id desc limit 1000000,10 耗时 5.2 秒

先筛选出 ID 放大查问范畴,写法如下:

select * from orders where id > (select id from orders order by id desc  limit 1000000, 1) order by id desc limit 0,10 耗时 0.5 秒

如果查问条件仅有主键 ID,写法如下:

select id from orders where id between 1000000 and 1000010 order by id desc 耗时 0.3 秒

如果以上计划仍然很慢呢?只好用游标了,感兴趣的敌人浏览 JDBC 应用游标实现分页查问的办法

其余数据库

作为一名后端开发人员,务必精通作为存储外围的 MySQL 或 SQL Server,也要积极关注 NoSQL 数据库,他们曾经足够成熟并被宽泛采纳,能解决特定场景下的性能瓶颈。

分类 数据库 个性
键值型 Memcache 用于内容缓存,大量数据的高拜访负载
键值型 Redis 用于内容缓存,比 Memcache 反对更多的数据类型,并能长久化数据
列式存储 HBase Hadoop 体系的外围数据库,海量结构化数据存储,大数据必备。
文档型 MongoDb 出名文档型数据库,也能够用于缓存
文档型 CouchDB Apache 的开源我的项目,专一于易用性,反对 REST API
文档型 SequoiaDB 国内出名文档型数据库
图形 Neo4J 用于社交网络构建关系图谱,举荐零碎等
退出移动版