关于MySQL的一些骚操作——提升正确性,抠点性能

概要

回顾以前写的项目,发现在规范的时候,还是可以做点骚操作的。
假使以后还有新的项目用到了MySQL,那么肯定是要实践一番的。
为了准备,创建测试数据表(建表语句中默认使用utf8mb4以及utf8mb4_unicode_ci,感兴趣的读者可以自行搜索这两个配置):

CREATE TABLE `student` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `no` int(11) unsigned NOT NULL DEFAULT ‘0‘ COMMENT ‘编号‘,
  `name` varchar(30) NOT NULL COMMENT ‘名称‘,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unq_no` (`no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

插入冲突时更新数据

SQL执行插入时,可能因为种种原因插入失败,比如UNIQUE索引冲突导致插入失败。比如某个不晓得DBA插入了一条错误的学生记录("3", "小明"),悲剧的是小明的编号是1。常规做法就是判断当前的数据库记录中是否存在小明的记录,如果有则更新其对应其编号,否则就插入小明的记录。当然存在更好的做法:

INSERT INTO student(`no`, `name`) VALUES (3, "xiaoming");
INSERT INTO student(`no`, `name`) VALUES (1, "xiaoming"), (2,"xiaohong")
ON DUPLICATE KEY UPDATE `no` = VALUES(`no`);

那就是使用ON DUPLICATE KEY UPDATE,这是mysql独特的语法(语句后面可以放置多个更新条件,每个条件使用逗号隔开即可)。需要注意,这里的VALUES(no)是将冲突的no数值更新为用户插入数据中的no,这样每条冲突的数据就可以动态的设置新的数值。

忽略批量插入失败中的错误

批量插入比单条数据挨个插入,普遍会提高性能以及减少总的网络开销。但是,假如批量插入的数据中心存在一个臭虫,在默认的情况下,这就会导致批量插入失败(没有一条数据插入成功)。当然,我们可以选择忽略,MongoDB都能够做到的事情,MySQL自然是可以做到。

INSERT INTO student(`no`, `name`) VALUES (1, "xiaoming");
INSERT IGNORE INTO student(`no`, `name`) VALUES (1, "xiaoming"), (2,"xiaohong"),(3, "xiaowang");

只需要在批量插入的语句中,插入IGNORE,那么某几条数据的插入失败就会被忽略掉,正确的数据依然可以插入库中。但是,我建议这个功能谨慎使用,使用mysql数据库本身就是看中数据的正确性,没必要为了批量插入的性能而自动放弃数据的正确性,如果真心觉得这个数据不重要,那么为什么不将此数据存入NoSQL中呢,MongoDB就是不错的选择。

IGNORE还有些副作用,感兴趣的可以自行查询。

使用JOIN替换子查询

MySQL的子查询优化不是太好,它的运行有点反我们的直觉(我们写的代码终究会在某些时候和我们的直觉相悖,这大概就是优化产生的根源之一吧)。其中最糟糕的一类是WHERE子句中包含IN的子查询语句(详情可见《高性能MySQL》一书的6.5章节,标题名字起得就很nice,为MySQL查询优化器的局限性)。概括下就是在部分情况下,在部分情况下MySQL可能会在挨个执行外部记录时执行子查询,如果外部记录数量较大,那么性能就会堪忧。

SELECT * FROM student WHERE no > (SELECT no FROM student WHERE `name`=‘xiaoming‘);
SELECT s.* FROM student s JOIN (SELECT no FROM student WHERE `name`=‘xiaoming‘) t ON s.no > t.no;

看上述代码,可以知道使用JOIN还是比较容易替换子循环,代码虽然会稍显晦涩,但是也许可以避免在并发量大的某个晚上你被叫起来检讨自己的错误。MySQL一直在优化子查询,在部分条件下子查询可能会比JOIN具有更高的效率,因此在有时间进行验证的情况下选择最佳的SQL语句。

JOIN中的WHERE和AND坑

为了更好的说明坑,我这里需要创建一个新的表,并在原来的学生表中添加字段:

CREATE TABLE `class` (
  `id` smallint(6) unsigned NOT NULL AUTO_INCREMENT,
  `no` int(10) unsigned NOT NULL COMMENT ‘编号‘,
  `name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT ‘名称‘,
  PRIMARY KEY (`id`),
  UNIQUE KEY `unq_no` (`no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `student`
  ADD COLUMN `cls_no` smallint(6) unsigned NOT NULL DEFAULT 0 AFTER `no`;

伪造一些数据,假设有4个班级,4班没有相对应的学生。使用如下的查询语句就能发现不同之处:

select c.*, s.`name` from class c left join student s on c.no = s.cls_no and c.no < 4 order by c.no asc;

查询结果如下图所示:

需要注意的是,此处我再查询条件中设置了c.no &lt; 4这一JOIN条件,但是明显的没有起到作用,查询结果中仍然显示了no=4的结果,这是因为此次查询使用的JOIN是LEFT JOIN,class作为左表,在匹配条件无法完全满足的情况下,亦会将左表的所有数据显示出来,引入了NULL值。
换成使用WHERE呢,参照下句:

select c.*, s.`name` from class c left join student s on c.no = s.cls_no where c.no < 4 order by c.no asc;

查询结果如下图所示:

为什么同样是使用LEFT JOIN,查询结果就不同了呢?这是因为可以认为SQL是分成两部分进行执行的(伪SQL,意思到位):

(1) select c., s.name?from class c left join student s on c.no = s.cls_no as tmn;
(2)select c.
, s.name?from tmp where c.no < 4 order by c.no asc;

需要注意的是,此处首先执行JOIN部分查询,再对查询结果执行WHERE。在执行INNER JOIN时,以上问题还可以忽略,但是如果使用的是LEFT JOIN或者RIGHT JOIN,则需要加倍小心查询条件了。

分页查询优化

查询的优化,最初是在研究MongoDB的分页查询时学到的,只能说大多数的数据库都是差不多的(当然现在存在时序数据库,分页查询那是更加骚气的)。大多数的分页查询都是类似如下的写法:

SELECT * FROM student WHERE cls_no > 1 LIMIT 1000, 10 ORDER BY id;

这样的写法存在性能损耗,数据库会将所有符合条件的数据查询出来,挨个数到第1000条记录,最后选取前10条记录进行交差。前面的1000条数据,就会显得很浪费,在LIMIT数值很大的情况下,这个性能损耗就是无法忍受的了(百度就会默认禁止查询76页以后的数据)。
因为分页一般是逐页翻下去的(如果是跳页进行查询,那就只能用上面的查询语句慢慢查询搜寻结果了),那么每次分页完都能获取当前的最大ID,我们可以基于ID确定我们的搜索起始点,基于此点向后查询10条满足要求的结果,改动如下(让前端多传一个当前页的最大ID,这个小小的要求当然是可以满足的):

SELECT * FROM student WHERE id > 1000 AND cls_no > 1 LIMIT 10 ORDER BY id;

以上是基于当前的ID是连续ID(其中若干记录没有被物理删除掉),如果是非连续ID,那么基于ID确定起始查询点是不恰当的,此时我们就可以使用JOIN:

SELECT s.* FROM student s JOIN (SELECT id FROM student LIMIT 1000, 10) t ON s.id = t.id;

其实,此处我们是id的索引表,从而快速的确定ID,因此查询简化成根据索引表查询的ID确定数据记录(不过需要注意,此处的索引表是无法添加WHERE子句的),因此这种写法在实际环境中几乎是个鸡肋。

UPDATE/DELETE改动多个表记录

工作中,经常需要修改多个表中的关联记录。一般的做法是将相关表中的记录查询出来,再挨个进行修改。如果修改的逻辑较为复杂,那么这样做是没有问题的,但是若是只是简单的修改(比如修改boolean变量),那么可以通过一条SQL语句完成此任务。
SQL中只要提及多个表,那么大致上就会出现JOIN的身影。我们有个需求,就是将3班的学生转移到5班(原有的3班更改为5班),使用JOIN语句的话就可以按照如下方式完成任务。

UPDATE student s JOIN class c ON c.no =3 AND c.no = s.cls_no SET c.no = 5, s.cls_no = 5; 

通过JOIN既可以完成此任务,可以拓展到修改多个表中数据内容,也可以扩展至DELETE语句中。

SELECT COUNT(*)/COUNT(1)/COUNT(列名)掉书袋

此处,就简单的总结一下:

  • SELECT COUNT(*):是SQL 92中定义的标准统计行数的语法(所以肯定是做了很多优化的);
  • SELECT COUNT(1): 查询符合条件的行数;
  • SLECT COUNT(列名): 查询符合条件的,且指定的列名所对应值非NULL行数。

对于SELECT COUNT(*)/COUNT(1),在MySQL的官方文档中,其实现思路是一样的,不存在性能差异,那么自然是推荐更加标准的写法了。

原文地址:https://blog.51cto.com/14230003/2450944

时间: 2024-08-30 03:19:32

关于MySQL的一些骚操作——提升正确性,抠点性能的相关文章

如何在命令长度受限的情况下成功get到webshell(函数参数受限突破、mysql的骚操作)

0x01 问题提出 还记得上篇文章记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门),我们讲到了一些PHP的一些如何巧妙地绕过数字和字母受限的技巧,今天我要给大家分享的是如何在命令长度受限的情况下成功get到webshell,以及关于函数参数受限的突破,mysql的一些骚操作技巧~~~ 0x02 问题分析 我们先看个例子: <?php $param = $_REGUEST['param']; if(strlen($param) < 17){ eval($param)

闪电侠 Netty 小册里的骚操作

前言 即使这是一本小册,但基于"不提笔不读书"的理念,仍然有必要总结一下.此小册对于那些"硬杠 Netty 源码 却不曾在千万级生产环境上使用实操"的用户非常有用.当然,对那些没有 Netty 编程经验的人来说,更为有用. 放个小册地址:[Netty 入门与实战:仿写微信 IM 即时通讯系统](https://juejin.im/book/5b4bc28bf265da0f60130116) 再次强烈推荐,一碗黄焖鸡/半杯 Luckin coffee/一包炫赫门 的价

Python 数据库骚操作 — MongoDB

前言 MongoDB GUI 工具 PyMongo(同步) Motor(异步) 后记 前言 最近这几天准备介绍一下 Python 与三大数据库的使用,这是第一篇,首先来介绍 MongoDB 吧,这里介绍 MongoDB 的两款操作库,走起!! MongoDB GUI 工具 首先介绍一款 MongoDB 的 GUI 工具 Robo 3T,初学 MongoDB 用这个来查看数据真的很爽.可以即时看到数据的增删改查,不用操作命令行来查看. PyMongo(同步) PyMongo 是一个同步操作的数据存

你没玩过的全新版本!Win10这些骚操作你知多少

你没玩过的全新版本!Win10这些骚操作你知多少 [PConline技巧]不知不觉,Win10与我们相伴已经整整四个年头了,从最开始的组团抗拒到现在的默默接受,个中滋味相信谁心里都有个数.近日微软开始推送"Win10更新五月版",那么Win10中到底都有哪些"骚"操作?一起来看看吧. 1. 夜间模式 都9102年了,我发现竟然还有小伙伴在使用第三方去蓝光软件."夜间模式"是Win10自带的一款去蓝光功能,能够快速去除屏幕蓝光.除了自定义去蓝光强度

spring boot 自动装配的实现原理和骚操作,不同版本实现细节调整,debug 到裂开......

开篇说明: 注解本身并没有什么实际的功能(非要说标记也是一个“实际”的功能的话,也可以算吧),隐藏在背后的注解处理器才是实现注解机制的核心.本篇将从这两个层面出发探索 spring boot 自动装配的秘密,并使用 spring boot 的自动装配机制来实现自动装配. 本次代码已经放到 github:https://github.com/christmad/code-share/tree/master/spring-boot-config-practice 代码中主要是做了 @Configur

大并发热点行更新的两个骚操作

大并发热点行更新的两个骚操作 要想db操作的性能足够高,巧妙的设计很重要,事务的操作范围要尽量的小.一般情况下我们都是使用某个orm框架来操作db,这一类框架多数的实现方式都是夸网络多次交互来开启事务上下文和执行sql操作,是个黑盒子,包括对 autocommit 设置的时机也会有一些差异,稍微不注意就会踩坑. 在大并发的情况下加上夸网络多次交互,就不可避免的由于网络延迟.丢包等原因导致事务的执行时间过长,出现雪崩概率会大大增加.建议在性能和并发要求比较高的场景下尽量少用orm,如果非要用尽量控

mysql数据库很简单操作

进入linux系统 root   >/usr/bin/mysql -u root mysql>show databases;                    #查看数据库 mysql>use  testtable;                        #testtable 为所要查看的库,应用数据库 mysql>show tables;                          #查看所有表 mysql>desc abc_table          

Mysql安装和基础操作

1.环境检查: 先检查是否已经安装了:rpm -qa |grep mysql ---两个都检查下,查看mysql是否安装 rpm -qa |grep MySQL若安装有可删除:rpm -e ****** 2.安装:1)安装前准备:mkdir usr/mysql cd进入该目录,上传安装包到/usr/mysql目录下2)安装:rpm -ivh MySQL-server-5.0.16-0.i386.rpm rpm -ivh MySQL-elient-5.0.16-0.i386.rpm3)验证安装是否

【代码学习】MYSQL数据库的常见操作

============================== MYSQL数据库的常见操作 ============================== 一.mysql的连接与关闭 -h:指定所连接的服务器位置 -u:数据库的用户名 -p:数据库的密码 1 mysql -u 用户名 -p //连接数据库 2 密码 3 exit //关闭数据库 1 mysql_connect($host,$user,$password); //连接数据库 2 mysql_close() //关闭数据库 二.创建数据