分享3个MySQL查询容易踩的坑

 更新时间:2022年11月07日 09:03:50   作者:言淦  
无论你是技术大佬,还是刚入行的小白,时不时都会踩到Mysql数据库的坑这篇文章主要给大家介绍了关于3个MySQL查询容易踩的坑,文中通过实例代码介绍的非常详细,需要的朋友可以参考下

前言

在使用MySQL的过程中,相比大家都踩过不少坑,下面我将列举日常生活中比较高频的三个”坑“,也欢迎大家踊跃分享自己的心路历程 🙃!

1、无 Order by 的随机问题

简单来说,就是在没有 Order By 的情况下,如果SELECT的字段不同,返回的记录顺序是随机的,不一定一样。

因为返回的记录顺序跟插入顺序、主键顺序、以及字段上是否建立了索引等因素都有关联,可以通过下面的例子进行验证:

假设有这样的一张用户点击表:

create table t_user_click
(
    id         int(11) auto_increment primary key,
    obj        varchar(64) default ''                not null comment '点击对象',
    click      smallint    default 0                 not null comment '点击数',
    remark     varchar(64) default ''                not null comment '备注',
    created_at timestamp   default CURRENT_TIMESTAMP not null comment '创建时间',
    updated_at timestamp   default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
)
    comment '用户点击表';

插入几条测试数据:

INSERT INTO t_user_click (id, obj, click, remark, created_at, updated_at) VALUES (1, 'aaa', 10, '', '2022-10-18 14:44:57', '2022-10-18 14:44:57');
INSERT INTO t_user_click (id, obj, click, remark, created_at, updated_at) VALUES (2, 'bbb', 20, '', '2022-10-18 14:44:57', '2022-10-18 14:44:57');
INSERT INTO t_user_click (id, obj, click, remark, created_at, updated_at) VALUES (3, 'ccc', 30, '', '2022-10-18 14:44:57', '2022-10-18 14:44:57');
INSERT INTO t_user_click (id, obj, click, remark, created_at, updated_at) VALUES (4, 'ddd', 40, '', '2022-10-18 14:44:57', '2022-10-18 14:44:57');
INSERT INTO t_user_click (id, obj, click, remark, created_at, updated_at) VALUES (5, 'eee', 50, '', '2022-10-18 14:44:57', '2022-10-18 14:44:57');
INSERT INTO t_user_click (id, obj, click, remark, created_at, updated_at) VALUES (6, 'fff', 60, '', '2022-10-18 14:44:57', '2022-10-18 14:44:57');

首先来看插入顺序带来的影响,在顺序插入几条顺序后,不管查询多少次,获取的结果都与插入顺序一致:

# 全部字段
mysql> select * from t_user_click where click > 0;
+----+-----+-------+--------+---------------------+---------------------+
| id | obj | click | remark | created_at          | updated_at          |
+----+-----+-------+--------+---------------------+---------------------+
|  1 | aaa |    10 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  2 | bbb |    20 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  3 | ccc |    30 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  4 | ddd |    40 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
...

# 与上述获取全部字段的SQL结果一致
mysql> select id, click from t_user_click where click > 0;
...

其次来看主键顺序这个因素,当该表进行过 DELETE/REPLACE/UPDATE 操作时,不会再按照插入顺序排序了,而是会按照主键ID进行排序。

# 进行update操作
UPDATE t_user_click SET id=7 WHERE id=3;

# 此时按照主键排序,obj=ccc 变成最后一条,而不是第3条
mysql> select * from t_user_click where click > 0;
+----+-----+-------+--------+---------------------+---------------------+
| id | obj | click | remark | created_at          | updated_at          |
+----+-----+-------+--------+---------------------+---------------------+
|  1 | aaa |    10 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  2 | bbb |    20 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  4 | ddd |    40 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  5 | eee |    50 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  6 | fff |    60 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  7 | ccc |    30 |        | 2022-10-18 14:44:57 | 2022-10-18 14:46:20 |
+----+-----+-------+--------+---------------------+---------------------+

# 与上述获取全部字段的SQL结果一致
mysql> select id, click from t_user_click where click > 0;
...

最后来看有无索引这个因素,当我们在 click 字段上建立索引时,结果又不一样了:

# 建索引
create index t_user_click_click_index on t_user_click (click);

# id=7 在最后一条
mysql> select * from t_user_click where click > 0;
+----+-----+-------+--------+---------------------+---------------------+
| id | obj | click | remark | created_at          | updated_at          |
+----+-----+-------+--------+---------------------+---------------------+
|  1 | aaa |    10 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  2 | bbb |    20 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  4 | ddd |    40 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  5 | eee |    50 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  6 | fff |    60 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  7 | ccc |    30 |        | 2022-10-18 14:44:57 | 2022-10-18 14:46:20 |
+----+-----+-------+--------+---------------------+---------------------+
6 rows in set (0.00 sec)

# id=7 在第3条
mysql> select id, click from t_user_click where click > 0;
+----+-------+
| id | click |
+----+-------+
|  1 |    10 |
|  2 |    20 |
|  7 |    30 |
|  4 |    40 |
|  5 |    50 |
|  6 |    60 |
+----+-------+

# 我们可以通过 explain 命令来看两种查询方式的不同:
# 两种方式一个没走索引,一个走了索引,从而导致返回结果的不同
mysql> explain select * from t_user_click where click > 0;
type:ALL
possible_keys:t_user_click_click_index
key:NULL
Extra:Using where

mysql> explain select id, click from t_user_click where click > 0;
type:index
possible_keys:t_user_click_click_index
key:t_user_click_click_index
Extra:Using where; Using index

2、Order by + Limit 的随机问题

简单来说就是进行**Order by的字段如果不唯一,则MySQL返回的记录是随机的,常见的表现就是数据分页后出现重复**。

不过,这种随机也不是随机算法那种打乱的随机,它跟数据的插入顺序,以及索引的建立也有一定关系,可以看看下面的例子。

假设有一张这样的用户表:

create table t_user_list
(
    id         int(11) auto_increment primary key,
    name       varchar(64) default ''                not null comment '名称',
    age        smallint    default 0                 not null comment '年龄',
    created_at timestamp   default CURRENT_TIMESTAMP not null comment '创建时间',
    updated_at timestamp   default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间'
)
    comment '用户表';

当你按顺序插入测试数据,然后再去分页查询,你可能会发现每次返回的结果都是固定的,也不会出现随机的情况(这时候跟插入顺序有关)。

# 插入测试数据
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (1, 'aaa', 1, '2022-10-18 12:55:19', '2022-10-18 12:55:18');
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (2, 'bbb', 2, '2022-10-18 12:55:18', '2022-10-18 12:55:18');
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (3, 'ccc', 3, '2022-10-18 12:55:18', '2022-10-18 12:55:18');
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (4, 'ddd', 4, '2022-10-18 12:55:18', '2022-10-18 12:55:18');
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (5, 'eee', 5, '2022-10-18 12:55:18', '2022-10-18 12:55:18');
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (6, 'ggg', 6, '2022-10-18 12:55:19', '2022-10-18 12:55:19');
INSERT INTO t_user_list (id, name, age, created_at, updated_at) VALUES (7, 'iii', 7, '2022-10-18 12:55:17', '2022-10-18 12:55:19');

# 分页查询
mysql> select * from t_user_list order by created_at limit 0, 3;
+----+------+-----+---------------------+---------------------+
| id | name | age | created_at          | updated_at          |
+----+------+-----+---------------------+---------------------+
|  7 | iii  |   7 | 2022-10-18 12:55:17 | 2022-10-18 12:55:19 |
|  2 | bbb  |   2 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
|  3 | ccc  |   3 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
+----+------+-----+---------------------+---------------------+
3 rows in set (0.00 sec)

mysql> select * from t_user_list order by created_at limit 3, 3;
+----+------+-----+---------------------+---------------------+
| id | name | age | created_at          | updated_at          |
+----+------+-----+---------------------+---------------------+
|  4 | ddd  |   4 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
|  5 | eee  |   5 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
|  1 | aaa  |   1 | 2022-10-18 12:55:19 | 2022-10-18 12:55:18 |
+----+------+-----+---------------------+---------------------+
3 rows in set (0.00 sec)

而当你这时候新建一个 created_at 索引,并重新插入新的数据时,如果你再次查询,你会惊奇地发现数据重复了。

# 新建索引
create index t_user_list_created_at_index on t_user_list (created_at);

# 插入新的测试数据
INSERT INTO db_article.t_user_list (id, name, age, created_at, updated_at) VALUES (8, 'jjj', 8, '2022-10-18 12:55:18', '2022-10-18 12:55:19');
INSERT INTO db_article.t_user_list (id, name, age, created_at, updated_at) VALUES (9, 'kkk', 9, '2022-10-18 12:55:18', '2022-10-18 12:55:19');
INSERT INTO db_article.t_user_list (id, name, age, created_at, updated_at) VALUES (10, 'mmm', 10, '2022-10-18 12:55:18', '2022-10-18 12:55:19');

# 再次分页查询 
mysql> select * from t_user_list order by created_at limit 0, 3;
+----+------+-----+---------------------+---------------------+
| id | name | age | created_at          | updated_at          |
+----+------+-----+---------------------+---------------------+
|  7 | iii  |   7 | 2022-10-18 12:55:17 | 2022-10-18 12:55:19 |
|  2 | bbb  |   2 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
|  3 | ccc  |   3 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
+----+------+-----+---------------------+---------------------+
3 rows in set (0.00 sec)

# id=3 的记录重复了
mysql> select * from t_user_list order by created_at limit 3, 3;
+----+------+-----+---------------------+---------------------+
| id | name | age | created_at          | updated_at          |
+----+------+-----+---------------------+---------------------+
|  3 | ccc  |   3 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
|  4 | ddd  |   4 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
|  5 | eee  |   5 | 2022-10-18 12:55:18 | 2022-10-18 12:55:18 |
+----+------+-----+---------------------+---------------------+

关于记录重复的问题,个人的猜想是一开始没有 created_at 索引,此时磁盘的数据块的顺序与插入顺序一致,所以返回的结果一直是固定的;但是加了索引后,并且增加了 created_at 一致的几条数据,导致索引重建数据块顺序发生变化,从而记录重复(瞎猜的,大家可以一起讨论下)

3、聚合函数 + Limit的不准确问题

简单来说,就是在对数据进行 Limit分页时,同时使用聚合函数(比如SUM、COUNT等)对当前分页的结果进行聚合,则最终得到的聚合结果是不准确的。

还是以第一点的例子和数据为例:

mysql> select * from t_user_click;
+----+-----+-------+--------+---------------------+---------------------+
| id | obj | click | remark | created_at          | updated_at          |
+----+-----+-------+--------+---------------------+---------------------+
|  1 | aaa |    10 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  2 | bbb |    20 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  4 | ddd |    40 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  5 | eee |    50 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  6 | fff |    60 |        | 2022-10-18 14:44:57 | 2022-10-18 14:44:57 |
|  7 | ccc |    30 |        | 2022-10-18 14:44:57 | 2022-10-18 14:46:20 |
+----+-----+-------+--------+---------------------+---------------------+

# 没分页的总数
mysql> select sum(click)  from t_user_click;
+------------+
| sum(click) |
+------------+
|        210 |
+------------+
1 row in set (0.00 sec)

# 分页后的总数
mysql> select sum(click)  from t_user_click limit 3;
+------------+
| sum(click) |
+------------+
|        210 |
+------------+

# 可以通过子查询解决
select sum(tmp.click)  from (select click from t_user_click limit 3) as tmp;

可以看到,在有Limit的情况下,得到的聚合结果其实是所有记录的总和,并不是三条记录的总和。原因就是SELECT语句执行时有一定顺序,分别是 FROM、ON、JOIN、WHERE、GROUP BY、HAVING、SELECT、DISTINCT、ORDER BY和LIMIT。

在上述SQL中,SELECT优先执行,即先执行 select sum(click) from t_user_click,这时已经得到结果为210,最终再执行 limit 3剔除不符合要求的记录。

总结

最后小结一下,本文主要介绍了MySQL查询中三个比较容易踩的坑,从中我们可以得到:

1、SELECT返回的顺序跟多种因素有关,如插入顺序、主键ID顺序、索引顺序等,如果你返回的结果要求有顺序,则记得加上 order by

2、加上了 order by 也不要高兴地太早,还需要检查 order by 的字段是否唯一,如果不唯一,返回的结果也有可能是随机的。

3、在进行聚合查询时,切记看有无 LIMIT 子句,有的话记得加上子查询避免查询的结果不准确。

到此这篇关于3个MySQL查询容易踩坑的文章就介绍到这了,更多相关MySQL查询易踩的坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL动态SQL拼接实例详解

    MySQL动态SQL拼接实例详解

    动态SQL呢?首先是SQL语句,是根据条件来拼接SQL,下面这篇文章主要给大家介绍了关于MySQL动态SQL拼接的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • MySQL延迟问题和数据刷盘策略流程分析

    MySQL延迟问题和数据刷盘策略流程分析

    这篇文章主要介绍了MySQL延迟问题和数据刷盘策略流程分析,本文要给大家提到了mysql复制流程,需要的朋友可以参考下
    2020-02-02
  • mysql间隙锁加锁11个规则(案例分析)

    mysql间隙锁加锁11个规则(案例分析)

    这篇文章主要介绍了mysql间隙锁加锁11个规则 ,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-03-03
  • Mysql计算字段长度函数之CHAR_LENGTH函数

    Mysql计算字段长度函数之CHAR_LENGTH函数

    mysql中计算字符串长度有两个函数分别为char_length和length,char_length函数可以计算unicode字符,下面这篇文章主要给大家介绍了关于Mysql计算字段长度函数之CHAR_LENGTH函数的相关资料,需要的朋友可以参考下
    2023-05-05
  • Mysql中幻读的概念以及如何解决

    Mysql中幻读的概念以及如何解决

    这篇文章主要介绍了Mysql中幻读的概念以及如何解决,幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行,需要的朋友可以参考下
    2023-05-05
  • MySQL删除表的外键约束图文教程(简单易懂)

    MySQL删除表的外键约束图文教程(简单易懂)

    删除表不是特别常用,特别是对于存在外键关联的表,删除更得小心,这篇文章主要给大家介绍了关于MySQL删除表的外键约束的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2024-07-07
  • MySQL 8.0.18 Hash Join不支持left/right join左右连接问题

    MySQL 8.0.18 Hash Join不支持left/right join左右连接问题

    在MySQL 8.0.18中,增加了Hash Join新功能,它适用于未创建索引的字段,做等值关联查询。这篇文章给大家介绍MySQL 8.0.18 Hash Join不支持left/right join左右连接,感兴趣的朋友一起看看吧
    2019-11-11
  • mysql如何配置白名单访问

    mysql如何配置白名单访问

    这篇文章主要介绍了mysql配置白名单访问的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-06-06
  • MySQL中的常用工具实例汇总(推荐)

    MySQL中的常用工具实例汇总(推荐)

    MySQL数据库以体积小、速度快、总体拥有成本低等优点,深受广大中小企业的喜爱,下面这篇文章主要给大家介绍了关于MySQL中常用工具的相关资料,需要的朋友们可以参考学习,下面来一起看看吧。
    2017-09-09
  • 详解MySQL日期 字符串 时间戳互转

    详解MySQL日期 字符串 时间戳互转

    本篇文章主要介绍了详解MySQL日期 字符串 时间戳互转,详解date转字符串、date转时间戳、字符串转date、字符串转时间戳、时间戳转date,时间戳转字符串,有兴趣的可以了解一下。
    2017-01-01

最新评论