Mysql ——关于SQL增删改以及创建表的优化

Mysql ——关于SQL增删改的优化

①大批量插入数据

如果同时执行大量的插入,建议使用多个值的 INSERT 语句(方法二)。这比使用分开 INSERT 语句快(方法一),一般情况下批量插入效率有几倍的差别。

方法一:

insert into T values(1,2); 
 
insert into T values(1,3); 
 
insert into T values(1,4);

方法二:

Insert into T values(1,2),(1,3),(1,4); 

选择后一种方法的原因有三:

减少 SQL 语句解析的操作,MySQL 没有类似 Oracle 的 share pool,采用方法二,只需要解析一次就能进行数据的插入操作。

在特定场景可以减少对 DB 连接次数。

SQL 语句较短,可以减少网络传输的 IO。

②适当使用 commit

适当使用 commit 可以释放事务占用的资源而减少消耗,commit 后能释放的资源如下:

事务占用的 undo 数据块。

事务在 redo log 中记录的数据块。

释放事务施加的,减少锁争用影响性能。特别是在需要使用 delete 删除大量数据的时候,必须分解删除量并定期 commit。

③避免重复查询更新的数据

针对业务中经常出现的更新行同时又希望获得改行信息的需求,MySQL 并不支持 PostgreSQL 那样的 UPDATE RETURNING 语法,在 MySQL 中可以通过变量实现。

例如,更新一行记录的时间戳,同时希望查询当前记录中存放的时间戳是什么?

简单方法实现:

Update t1 set time=now() where col1=1; 

Select time from t1 where id =1;

使用变量,可以重写为以下方式:

Update t1 set time=now () where col1=1 and @now: = now (); 

Select @now; 

前后二者都需要两次网络来回,但使用变量避免了再次访问数据表,特别是当 t1 表数据量较大时,后者比前者快很多。

④查询优先还是更新(insert、update、delete)优先

MySQL 还允许改变语句调度的优先级,它可以使来自多个客户端的查询更好地协作,这样单个客户端就不会由于锁定而等待很长时间。改变优先级还可以确保特定类型的查询被处理得更快。

我们首先应该确定应用的类型,判断应用是以查询为主还是以更新为主的,是确保查询效率还是确保更新的效率,决定是查询优先还是更新优先。

下面我们提到的改变调度策略的方法主要是针对只存在表锁的存储引擎,比如 MyISAM 、MEMROY、MERGE,对于 Innodb 存储引擎,语句的执行是由获得行锁的顺序决定的。

MySQL 的默认的调度策略可用总结如下:

写入操作优先于读取操作。

对某张数据表的写入操作某一时刻只能发生一次,写入请求按照它们到达的次序来处理。

对某张数据表的多个读取操作可以同时地进行。

MySQL 提供了几个语句调节符,允许你修改它的调度策略:

LOW_PRIORITY 关键字应用于 DELETE、INSERT、LOAD DATA、REPLACE 和 UPDATE。

HIGH_PRIORITY 关键字应用于 SELECT 和 INSERT 语句。

DELAYED 关键字应用于 INSERT 和 REPLACE 语句。

如果写入操作是一个 LOW_PRIORITY(低优先级)请求,那么系统就不会认为它的优先级高于读取操作。

在这种情况下,如果写入者在等待的时候,第二个读取者到达了,那么就允许第二个读取者插到写入者之前。

只有在没有其它的读取者的时候,才允许写入者开始操作。这种调度修改可能存在 LOW_PRIORITY 写入操作永远被阻塞的情况。

SELECT 查询的 HIGH_PRIORITY(高优先级)关键字也类似。它允许 SELECT 插入正在等待的写入操作之前,即使在正常情况下写入操作的优先级更高。

另外一种影响是,高优先级的 SELECT 在正常的 SELECT 语句之前执行,因为这些语句会被写入操作阻塞。

如果希望所有支持 LOW_PRIORITY 选项的语句都默认地按照低优先级来处理,那么请使用–low-priority-updates 选项来启动服务器。

通过使用 INSERTHIGH_PRIORITY 来把 INSERT 语句提高到正常的写入优先级,可以消除该选项对单个 INSERT 语句的影响。

查询条件优化

①对于复杂的查询,可以使用中间临时表暂存数据

②优化 group by 语句

默认情况下,MySQL 会对 GROUP BY 分组的所有值进行排序,如 “GROUP BY col1,col2,…;” 查询的方法如同在查询中指定 “ORDER BY col1,col2,…;” 。

如果显式包括一个包含相同的列的 ORDER BY 子句,MySQL 可以毫不减速地对它进行优化,尽管仍然进行排序。

因此,如果查询包括 GROUP BY 但你并不想对分组的值进行排序,你可以指定 ORDER BY NULL 禁止排序。

例如:

SELECT col1, col2, COUNT(*) FROM table GROUP BY col1, col2 ORDER BY NULL ;

③优化 join 语句

MySQL 中可以通过子查询来使用 SELECT 语句来创建一个单列的查询结果,然后把这个结果作为过滤条件用在另一个查询中。

使用子查询可以一次性的完成很多逻辑上需要多个步骤才能完成的 SQL 操作,同时也可以避免事务或者表锁死,并且写起来也很容易。但是,有些情况下,子查询可以被更有效率的连接(JOIN)…替代。

例子:假设要将所有没有订单记录的用户取出来,可以用下面这个查询完成:

SELECT col1 FROM customerinfo WHERE CustomerID NOT in (SELECT CustomerID FROM salesinfo )

如果使用连接(JOIN)…来完成这个查询工作,速度将会有所提升。

尤其是当 salesinfo 表中对 CustomerID 建有索引的话,性能将会更好,查询如下:

SELECT col1 FROM customerinfo 
   LEFT JOIN salesinfoON customerinfo.CustomerID=salesinfo.CustomerID 
      WHERE salesinfo.CustomerID IS NULL

连接(JOIN)…之所以更有效率一些,是因为 MySQL 不需要在内存中创建临时表来完成这个逻辑上的需要两个步骤的查询工作。

④优化 union 查询

MySQL 通过创建并填充临时表的方式来执行 union 查询。除非确实要消除重复的行,否则建议使用 union all

原因在于如果没有 all 这个关键词,MySQL 会给临时表加上 distinct 选项,这会导致对整个临时表的数据做唯一性校验,这样做的消耗相当高。

高效:

SELECT COL1, COL2, COL3 FROM TABLE WHERE COL1 = 10 
 
UNION ALL 
 
SELECT COL1, COL2, COL3 FROM TABLE WHERE COL3= 'TEST';

低效:

SELECT COL1, COL2, COL3 FROM TABLE WHERE COL1 = 10 
 
UNION 
 
SELECT COL1, COL2, COL3 FROM TABLE WHERE COL3= 'TEST';

⑤拆分复杂 SQL 为多个小 SQL,避免大事务

如下:

简单的 SQL 容易使用到 MySQL 的 QUERY CACHE。

减少锁表时间特别是使用 MyISAM 存储引擎的表。

可以使用多核 CPU。

⑥使用 truncate 代替 delete

当删除全表中记录时,使用 delete 语句的操作会被记录到 undo 块中,删除记录也记录 binlog。

当确认需要删除全表时,会产生很大量的 binlog 并占用大量的 undo 数据块,此时既没有很好的效率也占用了大量的资源。

使用 truncate 替代,不会记录可恢复的信息,数据不能被恢复。也因此使用 truncate 操作有其极少的资源占用与极快的时间。另外,使用 truncate 可以回收表的水位,使自增字段值归零。

⑦使用合理的分页方式以提高分页效率

使用合理的分页方式以提高分页效率 针对展现等分页需求,合适的分页方式能够提高分页的效率。

案例 1:

select * from t where thread_id = 10000 and deleted = 0 
   order by gmt_create asc limit 0, 15;

上述例子通过一次性根据过滤条件取出所有字段进行排序返回。数据访问开销=索引 IO+索引全部记录结果对应的表数据 IO。

因此,该种写法越翻到后面执行效率越差,时间越长,尤其表数据量很大的时候。

适用场景:当中间结果集很小(10000 行以下)或者查询条件复杂(指涉及多个不同查询字段或者多表连接)时适用。

案例 2:

select t.* from (select id from t where thread_id = 10000 and deleted = 0
   order by gmt_create asc limit 0, 15) a, t 
      where a.id = t.id;

上述例子必须满足 t 表主键是 id 列,且有覆盖索引 secondary key:(thread_id, deleted, gmt_create)。

通过先根据过滤条件利用覆盖索引取出主键 id 进行排序,再进行 join 操作取出其他字段。

数据访问开销=索引 IO+索引分页后结果(例子中是 15 行)对应的表数据 IO。因此,该写法每次翻页消耗的资源和时间都基本相同,就像翻第一页一样。

适用场景:当查询和排序字段(即 where 子句和 order by 子句涉及的字段)有对应覆盖索引时,且中间结果集很大的情况时适用。

建表优化(非常重要)

①在表中建立索引,优先考虑 where、order by 使用到的字段。

②尽量使用数字型字段(如性别,男:1 女:2),若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。

这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。

③查询数据量大的表会造成查询缓慢。主要的原因是扫描行数过多。这个时候可以通过程序,分段分页进行查询,循环遍历,将结果合并处理进行展示。

要查询 100000 到 100050 的数据,如下:

SELECT * FROM (SELECT ROW_NUMBER() OVER(ORDER BY ID ASC) AS rowid,* FROM infoTab)t 
WHERE t.rowid > 100000 AND t.rowid <= 100050

④用 varchar/nvarchar 代替 char/nchar。

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

不要以为 NULL 不需要空间,比如:char(100) 型,在字段建立时,空间就固定了, 不管是否插入值(NULL 也包含在内),都是占用 100 个字符的空间的,如果是 varchar 这样的变长字段, null 不占用空间。

猜你喜欢

转载自blog.csdn.net/weixin_43941676/article/details/113941013
今日推荐