概要

回顾以前写的项目,发现在规范的时候,还是可以做点骚操作的。

假使以后还有新的项目用到了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 < 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的官方文档中,其实现思路是一样的,不存在性能差异,那么自然是推荐更加标准的写法了。

PS:

如果您觉得我的文章对您有帮助,请关注我的微信公众号,谢谢!

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

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

    0x01 问题提出 还记得上篇文章记一次拿webshell踩过的坑(如何用PHP编写一个不包含数字和字母的后门),我们讲到了一些PHP的一些如何巧妙地绕过数字和字母受限的技巧,今天我要给大家分享的是如 ...

  2. 技术分享 | 在MySQL对于批量更新操作的一种优化方式

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 作者:景云丽.卢浩.宋源栋 GreatSQL社区原创内容未经授权不得随意使用,转 ...

  3. 通过HTTP的HEADER完成各种骚操作

    作为一名专业的切图工程师,我从来不care网页的header,最多关心Status Code是不是200.但是HEADER真的很重要啊,客户端从服务器端获取内容,首先就是通过HEADER进行各种沟通! ...

  4. 白日梦的MySQL专题(第33篇):各种登陆MySQL的骚操作

    阅读原文 系列文章公众号首发,点击阅读原文 前置知识 我们想登陆到mysql中前提是肯定需要一个用户名和密码:比如 mysql -uroot -proot 在mysql中用户的信息会存放在 mysql ...

  5. 闪电侠 Netty 小册里的骚操作

    前言 即使这是一本小册,但基于"不提笔不读书"的理念,仍然有必要总结一下.此小册对于那些"硬杠 Netty 源码 却不曾在千万级生产环境上使用实操"的用户非常有 ...

  6. 一波骚操作,我把 SQL 执行效率提高了 10,000,000 倍!

    作者:风过无痕-唐 http://www.cnblogs.com/tangyanbo/p/4462734.html 场景 我用的数据库是mysql5.6,下面简单的介绍下场景 课程表: create  ...

  7. 聊聊redis实际运用及骚操作

    前言 聊起 redis 咱们大部分后端猿应该都不陌生,或多或少都用过.甚至大部分前端猿都知道. 数据结构: string. hash. list. set (无序集合). setsorted(有序集合 ...

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

    你没玩过的全新版本!Win10这些骚操作你知多少 [PConline技巧]不知不觉,Win10与我们相伴已经整整四个年头了,从最开始的组团抗拒到现在的默默接受,个中滋味相信谁心里都有个数.近日微软开始 ...

  9. Python骚操作从列表推导和生成器表达式开始

    序列 序列是指一组数据,按存放类型分为容器序列与扁平序列,按能否被修改分为不可变序列与可变序列. 容器序列与扁平序列 容器序列存放的是对象的引用,包括list.tuple.collections.de ...

随机推荐

  1. Bootstrap基本CSS样式

    一.简介.使用 1.简介 Bootstrap 来源于 Twitter,是一款基于 Html.Css.JavaScript 的前端UI框架.可以方便.快速的开发web界面. 教程:https://www ...

  2. Python Web(四)

    Infi-chu: http://www.cnblogs.com/Infi-chu/ 一.Django-forms作用 前端和后端都要校验 前端校验的目的:减少后端的压力 用forms可以同时完成前端 ...

  3. [转]RPA认证 Developer UIPath Certificate,细说uipath认证学习,Online Quiz和Practical Exam项目详解

    本文转自:https://blog.csdn.net/u010369735/article/details/88621195 UIPath,RPA里算是比较简单易操作的一款软件了,因为公司业务的需要, ...

  4. apache部分报错解决方法

    AH00558: 进入apache文件夹下的conf文件夹,打开httpd.conf文件,用ctrl+F找到ServerName,如下图 在下面加上一句: ServerName domain_name ...

  5. 使用VSCode创建一个Vue项目

    vue-cli 是vue.js的脚手架,用于自动生成vue.js模板工程的. 安装vue-cli之前,需要先安装了vue和webpack · node -v          //(版本低引起:bas ...

  6. php对微信支付回调处理的方法(合集)

    支付完成后,微信会把相关支付结果和用户信息发送给商户,商户需要接收处理,并返回应答. 对后台通知交互时,如果微信收到商户的应答不是成功或超时,微信认为通知失败,微信会通过一定的策略定期重新发起通知,尽 ...

  7. tornado跨域解决方法

    代码 class BaseHandler(tornado.web.RequestHandler): # 允许跨域访问的地址 def allowMyOrigin(self): allow_list = ...

  8. echarts js报错 Cannot read property 'getAttribute' of null

    本文将为您描述如何解决 eharts.js报错 Uncaught TypeError: Cannot read property 'getAttribute' of null 的问题 根据报错信息查找 ...

  9. Linux和Windows的区别

    1. 软件与支持 • Windows 平台:数量和质量的优势,不过大部分为收费软件:由微软官方提供重要支持和服务: • Linux 平台:大都为开源自由软件,用户可以修改定制和再发布,由于基本免费没有 ...

  10. Tcp连接和断开

    三次握手:客户端为a,服务端为b:开始都是closed状态:a主动打开进入到syn_sent状态,b被动打开进入listen状态:第一次握手,a向b发送SYN=1,seq为x的包,b收到以后进入syn ...