MySQL 批量插入、批量更新

MySQL 批量插入/更新含有主键/唯一索引或者的解决方案是使用 on duplicate key update

解决方案

insert into database.table (id, key, field)
    values (v1, v2, v3), (v1, v2, v3), (v1, v2, v3)
    on duplicate key update
    id=values(id),key=values(key),field=values(field);

该方案适用范围广,
既可以批量插入,
也可以批量更新,
同时能保证避免插入重复数据,
还能避免影响自增 id,
又不需要创建临时表权限,
更不会修改已存在记录的其他字段。


实际应用中 update 后跟随的字段可以进行调整,保留只需要更新的字段


如果表中有自增 id,这个方案还有一点需要注意,insert into … values 跟随的记录数 (numberA)即为此次自增数 (numberA),即使存在重复唯一索引导致实际插入记录数(numberB)少于跟随的记录数(numberA)。


其他插入方案

1. insert ignore into

insert ignore 遇到重复数据会忽略,可以有效保证不插入重复数据,但也会忽略其他错误。
批量插入之后再进行单条更新。

# 批量插入,需要保证 NOT NULL 字段有值
insert into database.table (id, key, field)
    values (v1, v2, v3), (v1, v2, v3), (v1, v2, v3);

# 单条更新
update database.table set field = v3 where key = v1;
update database.table set field = v3 where key = v1;
update database.table set field = v3 where key = v1;

2. replace into

如果表中有主键或唯一索引,replace into 会删除冲突行(已存在记录),再新插入新行。因此导致 replace into 可能会导致主键自增 1。
如果表中没有主键和唯一索引,replace into 没有意义。

如果表中没有自增主键,使用 replace into 是一个不错的选择。

使用 replace 需要注意,如果 更新的字段 不全,会被设置为缺省值,也就是会清除其他字段。

replace into database.table (id, key, field)
    values (v1, v2, v3), (v1, v2, v3), (v1, v2, v3);

最后,推荐使用 insert into … on duplicate key update

on duplicate key update 是 mysql 特有语法。

MySQL root 密码丢失重置

前几周百度云同步时把重要文件误删,包括 mysql root 密码。

MySQL root 密码丢失,需要以类似 mysqld_safe –skip-grant-tables 方式运行,才能让 mysql -uroot 顺利执行,登录到 mysql 命令行。

如果 mysql 在运行,先将 mysql 运行方式记录下来

> ps aux | grep mysql
// 假设当前 mysql 运行命令如下,实际参数可能更复杂
/mysql/bin/mysqld --pid-file=/mysql/mysql.pid --socket=/tmp/mysql.sock

1. 关闭 mysql 进程

不要使用 kill 命令直接结束,而是通过 stop 参数结束 mysql 进程。

// 假设 mysql 启动命令是
// /etc/init.d/mysql start
// 则应该通过如下方式结束 mysql 进程
> /etc/init.d/mysql stop

2. 以 –skip-grant-tables 方式启动 mysql

在原有 mysql 运行命令基础上加上 –skip-grant-tables &,& 表示放入当前会话 background。

/mysql/bin/mysqld --pid-file=/mysql/mysql.pid --socket=/tmp/mysql.sock --skip-grant-tables &

3. 登录 mysql 修改 root 密码

> mysql -uroot
> update mysql.user set password=PASSWORD('MysqlRoot') where user='root';

4. 参考第 1 步,结束 mysql 进程

5. 执行原有 mysql 启动命令

// 假设 mysql 启动命令如下
> /etc/init.d/mysql start

使用 root 新密码登录 mysql

> mysql -uroot -p
Enter password: 

密码重置完成。

重置过程有两点需要注意:

  1. 不推荐使用 kill 结束 mysql 进程;
  2. 以 –skip-grant-tables 方式运行 mysql 需要保留原有参数

MySQL设置root远程访问、内网访问

使用 root 登录 mysql 服务器

use mysql;
// 设置 root 支持来自任意客户端可以访问
update user set host = ‘%’ where user = ‘root’;

// 设置 root 支持来自内网 IP 192.168.* 可以访问
update user set host = ‘192.168%’ where user = ‘root’;

user 中可能有多条 root 记录,执行上述语句更新时,可以先查询出 host、user 列表(select host,user from mysql.user),执行 update 语句时增加一个 where 条件判断,只更新其中一个 host。

合理设置联合主键,避免无效索引

设计有效的 MySQL 联合主键。

以 MySQL 存储用户笔记为例,设计两种数据库 —— 表结构一致、联合主键顺序不一致。

联合主键表结构

t_note_no 表结构

联合主键表结构

t_note_yes 表结构

可以看出两个数据库在数据存储上是一致的,唯一区别是联合主键顺序不一致。

查询笔记

查询指定用户的正常状态的笔记:

select * from t_note_no where user_id=1 and status=1;
select * from t_note_yes where user_id=1 and status=1;

以上两条语句在表数据量不大的情况下无法看出区别。

当数据量达到十万级(实际情况跟机器性能有关)时差异开始体现出来,第一种查询会明显慢。

联合主键 explain

使用 explain 语句解释可以发现 “select * from t_note_no where user_id=1 and status=1” 并没有用到主键索引。而 “select * from t_note_yes where user_id=1 and status=1” 是用到了索引。

explain 语句说明:

id select 查询的序列号
select_type select 查询的类型,主要是区别普通查询和联合查询、子查询之类的复杂查询
table 表示引用的表
type 表示查询类型
possible_keys 表示 MySQL 可能使用的索引,没有使用索引时为 NULL
key 表示 MySQL 实际使用的索引,没有使用索引时为 NULL
key_len 表示 MySQL 使用的索引的长度,越短越好
ref 表示使用索引的哪一列
Extra 额外信息
  • select_type
  • SIMPLE 简单 select,没有使用 union 或子查询
    PRIMARY 最外面的 select
    UNION union 中的第二个或后面的 select 语句
    DEPENDENT UNION union 中的第二个或后面的 select 语句,取决于外面的查询
    UNION RESULT union 的结果
    SUBQUERY 子查询中的第一个 select
    DEPENDENT SUBQUERY 子查询中的第一个 select,取决于外面的查询
    DERIVED 导出表的 select,是 from 子句的子查询
  • type
  • system 系统表,仅有一行,是 const 联接类型的一个特例
    const 表最多有一个匹配行,在该行的列值被认为是常数,只会读取一次,非常快速
    eq_ref 从每个来自前面的表的行组合中读取一行,可能是除了 const 之外最好的联接类型
    ref 从对于每个来自前面的表的行组合读取有匹配索引值的行
    ref_or_null 添加 MySQL 可以专门搜索包含 NULL 值的行,可以理解如同 ref
    index_merge 表示使用了索引合并优化方法
    unique_subquery 表示替换了指定形式【value IN (SELECT primary_key FROM single_table WHERE some_expr)】的 IN 子查询的 ref,是一个索引查找函数,可以完全替换子查询,效率更高
    index_subquery 类似于 unique_subquery。可以替换指定形式【value IN (SELECT key_column FROM single_table WHERE some_expr)】的 IN 子查询,并且是非唯一索引
    range 只检索给定范围的行,使用一个索引来选择行
    index 表示进行完整的索引扫描,因为索引文件通常比数据文件小,所以通常比 ALL 快
    ALL 表示进行完整的表扫描,此为最差的情况

    最好保证查询能达到 ref,再不济也要缩小到一定范围,达到 range。

  • Extra
  • Distinct 匹配到第1行之后停止匹配
    Not exists MySQL 对查询进行 LEFT JOIN 优化,发现1个匹配 LEFT JOIN 标准的行后,不再为前面的的行组合在该表内检查更多的行
    range checked for each record (index map: #) MySQL 没有发现好的可以使用的索引,但发现如果来自前面的表的列值已知,可能部分索引可以使用
    Using filesort MySQL 需要额外的一次传递,以找出如何按排序顺序检索行
    Using index 从只使用索引树中的信息而不需要进一步搜索读取实际的行来检索表中的列信息
    Using temporary 为了解决查询,MySQL 创建一个临时表来容纳结果
    Using where WHERE 子句用于限制哪一个行匹配下一个表或发送到客户
    Using sort_union(…), Using union(…), Using intersect(…) 这些函数说明如何为 index_merge 联接类型合并索引扫描
    Using index for group-by 类似于访问表的 Using index 方式,Using index for group-by 表示 MySQL 发现了一个索引,可以用来查询 GROUP BY 或 DISTINCT 查询的所有列,而不要额外搜索硬盘访问实际的表

    设计联合主键尽可能根据实际应用考虑。

不合理的查询导致索引失效

SQL语句不合理也会导致索引失效,例如:

select * from t_note_yes where user_id=1 or status=2;

where 查询条件中虽然 user_id 设置了索引,但是 status 没有设置索引,导致索引失效。而且对 status tinyint 类型的字段设置索引是没有必要为其设置索引。

知晓了 explain 各个参数的含义,可以快速帮助提高数据库查询性能。

但是在很多情况下也会导致索引失效。

如果 where 子句中使用 like,并且在左侧使用了 %,那便没有使用索引。

SQL WHERE LIKE

如果 where 子句中使用 or,并且 or 的各个条件只要有一个没有使用到索引,那也会导致整个 SQL 查询无法使用索引。

SQL WHERE OR

可以看出 mysql 按照最差情况处理。

在软件开发或者检查数据库性能的过程中,如果无法确认 mysql 是否使用了索引,立即使用 explain 解释 SQL 语句,就能够看到本质。