安装方法
Mysql
与MariaDB
版本对应关系
CentOS:使用包的方式安装最新MariaDB,CentOS安装client直接yum install mysql而不是client,而安装mysql则直接用yum install -y mysql mysql-server mysql-dev mysql-devel
,CentOS7上已经用mariadb代替了mysql,这样子使用:
1 | yum install mariadb-server mariadb-client mariadb-devel -y |
另外,更新方式可以参考这篇文章: 如何更新到MariaDB 10.4
Ubuntu:
1 | 安装最新版本mariadb,需要先导入对应的镜像库https://downloads.mariadb.org/mariadb/repositories |
修改mysql数据目录
通过软链接更改目录:
1
2
3
4systemctl stop mysql
mv /var/lib/mysql /mnt/data # 先将原来的数据目录移动到新地址
ln -s /mnt/data/mysql /var/lib # 再做软链接
systemctl start mysql # 重启mysql即可直接更改目录,如果无法启动,可以查看
/var/log/mysql
里面的错误日志,可能是apparmor有问题,如果是这个问题,可以这样修改:1
2
3vim /etc/apparmor.d/usr.sbin.mysqld,会看到下面的配置,修改为新的目录即可
/var/lib/mysql/ r,
/var/lib/mysql/** rwk,
常用命令
SQL文件操作
1 | 执行sql文件 |
数据库操作
1 | # 创建数据库,如果是gbk编码,分别用gbk、gbk_chinese_ci; |
数据表操作
- 给字段设置默认值的时候可以使用表达式,并且外面必须加括号,例如给
json
数据设置默认值可以为DEFAULT (JSON_ARRAY())
1 | CREATE TABLE `table2` SELECT * FROM `table1`; # 从一张旧表直接建立一张新表 |
常见表字段
- MySQL每行最大能存储65535字节的内容,所以对于utf8最多存储21844个字符,对于utf8mb4最多存储16383个字符,这也是
VARCHAR/Text
的最大值,MediumText
长度为16777215。 - 如果是存储
ip
信息的字段一定要兼容IPv6
(如果用字符存储那么最长39位)
timestamp
TIMESTAMP(3)/TIMESTAMP(6)
表示精确到毫秒微妙级别- 对于timestamp字段,如果直接插入数字形式的时间戳可能会变成
0000-00-00 00:00:00
的结果,我们需要使用FROM_UNIXTIME(1234567890)
函数对其进行转换
decimal
- 定义时候需要提供两个参数:
DECIMAL(P, D)
,其中P表示有效数字的精度,范围为1-65
,D表示小数点后的位数,范围是0-30
,其中D<P
数据增删改查
LEFT JOIN
是LEFT OUTER JOIN
的简写,RIGHT JOIN
是RIGHT OUTER JOIN
的简写,JOIN
是INNER JOIN
的简写- 获取某个表的自增下一个自增ID:
SHOW TABLE STATUS
,结果中的auto_increment
查询
- 在程序中遇到要拼接
SQL
语句的,可以在条件里面加一个where 1=1
能有效简化代码 - 特别注意可以为
NULL
的字段,如果where field != 'false'
,那么为NULL
的不会被查询出来,必须加上or field IS NULL
,注意In(NULL)
也是不允许的
1 | # 普通查询 |
LIKE查询的特殊转义
1 | /: // |
连表查询
1 | # LEFT JOIN ... ON ... |
修改/更新
1 | ## 更改某字段的值,特别需要注意的是,mysql和mariadb是没有update from的,sql server才有。更新的时候WHERE语句一定是在SET语句后面,而JOIN语句则是在SET语句前面 |
删除
1 | DELETE `deadline` FROM `deadline` LEFT JOIN `job` # 有LEFT JOIN情况时删除指定表的数据 |
插入
1 | 插入数据 |
锁
- 常用于:并发读写数据防止读写到错误的数据(例如,两个请求在两个事务中同时对同一个字段执行
+10
的操作,那么可能出现总共+20
,也可能出现只+10
的情况) UPDATE
和DELETE
语句本身就会对行加锁,但是SELECT
默认不会,需要显式加锁S
锁(共享锁,读锁):如果在事务里面读取默认是读锁,该事务内无法对其进行修改(要修改必须获取X锁),同时,其他事务也只能对该数据加S锁,不能加X锁。X
锁(排他锁,写锁):该事务内可以读写,其他事务在这其间不能对数据加任何的锁。
悲观锁
默认认为需要修改的数据是会发生
共享锁:其他事务可读,但不可写
1
SELECT ... LOCK IN SHARE MODE # 共享锁,其它事务可读,不可更新
排他锁:其他事务不可读写
1
SELECT ... FOR UPDATE # 排它锁,其它事务不可读写
乐观锁
- 具体实现逻辑其实是自己实现的
- 如果重试,对性能有一定的影响
默认认为需要修改的数据是不会发生冲突的,在更新之间是不会有任何锁的。
有些实现方法是单独加入了一个版本号码字段,但是如果是字段特殊,并且业务不大复杂,可以直接使用某个需要更新的字段作为版本,例如
1 | SELECT * FROM `user` WHERE `id`=1; # 先普通查询出用户数据 |
然后在更新操作执行完成后获取影响的行数,如果影响行数为0,表示更新操作不起作用,版本已经发生变化,这时候就需要用户自己去抛错或者编写重试逻辑(重试的时候会重新获取字段值即版本号)。
存储过程/函数
1 | DROP PROCEDURE name; # 删除存储过程 |
事务
- MySQL的几种事务隔离性:
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ: 默认的事务隔离级别,可重复读。
- SERIALIZABLE
系统相关
1 | 更改密码 |
数据库维护
- mysqldump参数
--default-character-set=utf-8
指定导出的字符编码
1 | 备份整个数据库 |
binlog
数据库的sql日志。
使用canal
可以很方便地监听数据库的所有操作。
Hint
可以指定查询优化的方式
1 | FORCE INDEX # 强制指定索引 |
帮助函数
1 | # 字符串相关 |
数据库优化
- EXPLAIN可用于查看语句的执行计划
常见性能问题及优化
- COUNT(*)优化: Innodb数据库中表的总行数并没有直接存储,而是每次都执行全表扫描,如果表太大简单的
COUNT(*)
则会非常耗时。这时候不妨选择某个字段添加一个辅助索引,依然会扫描全表,但是COUNT(*)
的性能能提高很多。因为在使用主键或者唯一索引的时候,InnoDB会先把所有的行读到数据缓冲区,发生了多次IO,而使用了辅助索引以后,由于辅助索引保存的仅仅是index的值,虽然还是读了那么多行到缓冲区,但是数据量则大大减少,仅有一个字段,磁盘IO减少,所以性能提高了。 - char和varchar: Char是定长类型,对于经常变更的数据,一般采用CHAR来进行存储,因为CHAR类型在变化的时候不容易产生碎片。VARCHAR是变长类型,它比CHAR更节省空间。
- 使用ENUM枚举类型来代替字符串类型
- LIKE查询优化: 如果是
abc%
型的like
查询是能用到该字段的索引的,如果是前后都模糊搜索,那么最好是加一个有索引的字段进行筛选,例如时间 - 对于Limit语句,即使where条件有索引,在数据量太大的时候仍然会有问题: 例如,
LIMIT 10000000000 10
即使只取10条数据依然会很慢,好的做法是每次查询将上一次查询的末尾值拿到,然后在下次查询的时候将该值放入查询中,例如WHERE time > 'xxx' ORDER BY time LIMIT 10
即可。 wait_timeout
设置: 最好将全局的wait_timeout
设置为120,防止因为慢sql太多导致数据库性能变慢,特别是针对大企业的公共数据库。并且连接自己设置的wait_timeout
依然首先会受到全局设置的影响,当wait_timeout
超时后会出现2013: Lost connection to MySQL server during query错误
索引类型
- 注意,外键并不会创建索引
唯一索引
- 注意唯一索引不能建在可以为NULL的字段上,否则,该唯一索引在NULL上不会生效,可以参考底部关于软删除的文章,简单地说,对于唯一索引的咧,只要有一行是null,那么唯一索引就不会生效
聚簇索引(clustered index)
- 索引必须为唯一索引,局促索引不一定是主键,但是主键一定是局促索引
- 叶子结点存储的是整行数据,所以查询速度非常快
- 如果没有主见,那么聚簇索引可能是第一个不允许为null的唯一索引
保存了每一样的所有数据,聚簇索引的选择方法如下:
1 | 1.如果表中定义了PRIMARY KEY,那么InnoDB就会使用它作为聚簇索引; |
辅助索引(secondary index)
聚簇索引以外的就是辅助索引,辅助索引的每一行记录都包含每一行的主键列,辅助索引指向主键,想较于聚簇索引,由于只有一个字段,所以空间占用非常少。当然这就导致肯定需要回表查询,即拿着聚簇索引去查找该行数据
覆盖索引
当sql语句的所求查询字段(select列)和查询条件字段(where子句)全都包含在一个索引中 (联合索引),可以直接使用索引查询而不需要回表。
排序算法
filesort文件排序
文件排序是通过相应的排序算法,把所有的数据拿出来之后在内存中进行排序。使用firlesort排序主要是因为where语句与order by语句使用了不同的索引;order by中的列的索引不同;对索引同时使用ASC和DESC;left join使用右表字段排序等。
TroubleShooting
启动错误,提示server PID file could not be found
一般是因为MySQL服务卡死了,此时查看进程
ps aux | grep mysql*
,然后把卡死的给kill掉就行了Access denied for user ‘root‘@’localhost’
出现这种情况,可能是给用户分配了’%’权限,而没有分配localhost权限,我去…
WorkBench保持连接不断开:
Edit->Preferences->SQL Editor,设置DBMS connection read time out(in seconds)
- 关于整型数据长度问题,需要注意的是MySQL里面的整型后面跟的长度并不是指该字段的实际长度,而是客户端显示的长度,实际存储的长度可以更长。这是几个整型数据对应的长度表(来自MySQL官网),所以
INT
无论后面定义的是多少,都是4个字节32位的长度
Type | Storage | Minimum Value | Maximum Value |
---|---|---|---|
(Bytes) | (Signed/Unsigned) | (Signed/Unsigned) | |
TINYINT | 1 | -128 | 127 |
0 | 255 | ||
SMALLINT | 2 | - 32768 | 32767 |
0 | 65535 | ||
MEDIUMINT | 3 | - 8388608 | 8388607 |
0 | 16777215 | ||
INT | 4 | - 2147483648 | 2147483647 |
0 | 4294967295 | ||
BIGINT | 8 | - 9223372036854775808 | 9223372036854775807 |
0 | 18446744073709551615 |
MySQL分页时出现数据丢失或者数据重复的情况: 如果分页的时候用上了
order_by
并且目标字段并不是索引字段,那么就有可能出现这种情况,一条数据可能既出现在上一页,又出现在下一页。原因是在mysql5.6
以后,priority queue
使用的是堆排序,这个排序算法并不稳定,两个相同的值可能在两次排序后的结果不一样。解决方法有两种,一种是给order_by
后面的字段加索引,另外一种是增加一个是索引的字段,但是不要把主键放到这里面,否则两个索引都不会使用,导致性能非常低,别问我为什么,我被坑过。参考文章在查询整型字段的时候空字符串表现得和0一样: 这是MySQL的特性,对于整型字段,空字符串会自动转换成零。另外,对于
timestamp
字段''
和0000-00-00 00:00:00
表现得一样,插入NULL
到不能为NULL
的timestamp
字段时,既不会报错又不会插入空值,而是会变成当前的时间。插入’’和使用’’去读取可能会有warning,甚至mysql和mariadb表现不同,可能导致查询不到数据,所以建议都用0000-00-00 00:00:00**timestamp字段插入的时候出现
warnning: data truncated for column
**,这是因为mysql
的timestamp
类型不是unix
的时间戳,对于非法的字符串插入timestamp
的时候结果都是0000-00-00 00:00:00
。如果要插入,可以用2017-12-25 12:00:00
这种格式,或者使用函数FROM_UNIXTIME(1514177748)
进行转换。Invalid use of NULL value: 原因可能是在将列修改为不允许NULL的时候并且已经存在记录该值为null,则不允许修改,这个时候需要先修改已有记录的值。
PhpMyAdmin查询正确,但是导出结果时导出的文件里面只有一条错误的sql语句: 尝试把要导出的字段及表名不用别名
2038问题: 由于历史原因,
TIMESTAMP
最多只能存储到2038-01-19 05:14:07
,超过则会报错或者被置为NULL,目前暂时还没有解决办法,但是我相信到时候那帮牛人肯定会直接在数据库程序层面解决的,而不是我们去更改程序。当然,如果用DATETIME
倒是可以多存储到子子孙孙那里,但是却没有时区概念。现在距离那个时间点还有20年,我的建议是,如果字段是作为创建时间、更新时间、删除时间这种,精度要求比较高并且时区不允许错乱(事实上,所有项目时区都是要有要求的,不能保证每个人使用或者每个服务器的时区是一样的),就可以用TIMESTAMP
,像记录某个历史事件、或者万年历、生日这种才需要用DATETIME
Table is specified twice, both as a target for ‘UPDATE’ and as a separate source for data in mysql: 在
10.1.24-MariaDB
有问题,但是10.3.7-MariaDB
上没有问题,应该跟版本有关,解决办法就是在子查询外面再嵌套一层select * 表名 as 新表名
。column “c.name” must appear in the GROUP BY clause or be used in an aggregate: 见于SQL与MySQL语法不兼容的情况,在SQL3标准以前,选择显示的字段必须出现在
GROUP BY
中。解决办法要么是将该字段加入GROUP BY
,要么在子查询中完成聚合,在外部在获取字段。field isn’t in GROUP BY: 在查询前先设置
SET SQL_MODE=only_full_group_by
数据写入成功但是却读取不到: 其中一种原因是使用
mysqldump
进行备份的时候,默认会给数据表加锁,此时如果写入数据,那么主库会写入成功(肯定是在从库进行dump),但是此时从库上了锁,数据更新有延迟。解决办法是错开高并发写入的时间进行备份,另一种是使用不会锁表的备份方式如何实现上一篇下一篇功能: 直接在排序好的基础上用大于小于即可,例如:
1
2
3SELECT * FROM `posts` WHERE id=3;
SELECT * FROM `posts` WHERE id>3 LIMIT 1;
SELECT * FROM `posts` WHERE id<3 LIMIT 1;Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. You have to change some columns to TEXT or BLOBs,这是一行的长度超过了65535个字节的限制,一般是因为字段过大或者字段过多,例如
varchar(255)
就能存储255个字符,然而一个字符要占3个字节,就相当于有765个字节了。遇到这种情况,首先应该按实际情况减少部分字段的长度,如果字段不能减少,长度仍然不能减少,就只有用TEXT
或者BLOBs
来存储部分字段了,这两种类型不算在65535内。**User ‘xxx’ has exceeded the ‘max_user_connections’ resource (current value: 10)**,原因是超出了设置的单个用户的最大连接数(可以使用
select @@max_user_connections;
进行查看),默认为0表示无限制,单如果大于零并且超过了就会出现该错误。可以这样修改set @@global.max_user_connections=1;
某个语句一直卡住,或者无法修改表结构,但是又找不到表锁,可能的原因是客户端有未关闭或提交的事务,会出现
waiting for table metadata lock
,可以先使用select * from information_schema.innodb_trx;
查看当前有哪些事务锁,然后用KILL thread_id
杀掉该锁进程。Mariadb/Mysql不锁表实时添加列:
10.2
开始是默认支持的,但是只能在表最后一列后加,不能出现after
,参考https://mariadb.com/kb/en/library/instant-add-column-for-innodb/mysqldump出现Access denied for user xxx when using LOCK TABLES: 可以在
mysqldump
命令添加上--single-transaction
参数mysql8使用group出现only_full_group_by错误: 执行一下sql命令即可:
1
2set global sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';
set session sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';Syntax error or access violation: 1059 Identifier name is too long: 原因是设置的键的名字的长度太长了,只需要将索引键的名字改短即可,laravel的migrate中这样指定
1
$table->unique(['field1', 'field2', 'field3', 'field4'], 'myfield');
**SQLSTATE[22007]: Invalid datetime format: 1292 Truncated incorrect DOUBLE value: ‘’**: 可能是把一个整数用在了
varchar
上,例如where name = 123456
,其中123456
没有打引号而是直接作为整数在查询Invalid datetime format: 1292 Incorrect datetime value: ‘1602554081’: 应该是在
TIMESTAMP
类型的字段上真的传入了一个TIMESTAMP
,应该传入例如2020-02-22 22:22:22
这样的字符串mysqldump 出现 Unknown table ‘COOLUMN_STATISTICS’ in information_schema: 只需在
mysqldump
添加参数--column-statistics=0
将逗号分割的字符串转换为Array的形式:
1
2
3
4
5SELECT
CAST(
CONCAT('["', REPLACE(REPLACE(`field`, '"', '\"'), ',', '","'), '"]')
AS JSON
);
Different lower_case_table_names settings for server (‘0’) and data dictionary (‘1’): 这是因为Mysql8开始新增了
data dictionary
的概念,数据初始化时会使用lower-case-table-names=0
,数据库启动时则会读取my.cnf
文件中的值,如果两者不一致就会报错,这时候可以直接修改my.cnf
中的[mysqld]
下添加lower_case_table_names = 1
ERROR: Specified key was too long; max key length is 3072 bytes: 一般是因为要做索引的字段长度太长了,但是有时候看起来并不长,仍然报错,那么可以在配置文件的
[mysqld]
下添加innodb_large_prefix=1
,然后重启mysql即可mysqldump出现unknown information_schema(1109) COOLUMN_STATISTICS: 需要添加参数
--skip-column-statistics
Mac使用mysqldump:
1
2
3
4
5
6brew install mysql
如果在dump的时候报错does not include mysql_native_password需要降级
brew install mysql-client@8.4
brew unlink mysql
brew link mysql-client@8.4
扩展阅读
- 记一次神奇的Mysql死锁排查: 一种非常隐蔽的发生死锁的情况。
- 软删除之痛: 软删除很好用,但还是具体场景具体分析,不要一味地用,需要考虑数据是否有软删的必要,和如何解决软删的副作用