这一份笔记以 《MySQL必知必会》为基础,按照个人需求持续补充。
完善中...不仅限于该入门书上的知识
由于原文是在Typora写的, 因此部分图片可能会没有上传上来, 有空后续会补上.
最后修改时间: 2019年10月23日15:38:40
基础概念
MySQL 的两种发音:
- My-S-Q-L
-
sequel
['siːkw(ə)l]
数据库中的 schema : 关于数据库和表的布局及特性的信息
有时,schema 用作数据库的同义词。遗憾的是,schema 的含义通常在上下文中并不是很清晰。
主键(primary key): 一列(或一组列), 其值能唯一区分表中每一行。
- 任意两行都不具有相同的主键值
- 每个行都必须具有一个主键值(不允许为NULL值)
- 使用多个列作为主键值, 多个列值的组合必须是唯一(但单个列的值可以不唯一)
子句(clause) SQL语句由子句构成,有些子句是必需的,而有的是可选的。
- 一个子句通常由一个关键字和所提供的数据组成。
子句的例子有 SELECT 语句的 FROM 子句,
MariaDB 与 MySQL 版本替代:
- MySQL 5.1 -> MariaDB 5.1, 5.2, 5.3
- MySQL 5.5 -> MariaDB 5.5, 10.0
- MySQL 5.6 -> MariaDB 10.0
- MySQL 5.7 -> MariaDB 10.2
安装
CentOS 使用yum源安装
下载对应的yum仓库: https://dev.mysql.com/downloa...
# RHEL6
wget https://dev.mysql.com/get/mysql80-community-release-el6-3.noarch.rpm
# RHEL7
wget https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
#### 以下以 RHEL6(CentOS) 为例 ####
#### CentOS 7 一些命令不大一样, eg. service, chkconfig ####
# 安装yum源
yum localinstall mysql80-community-release-el6-3.noarch.rpm
# 确认是否已成功安装
yum repolist enabled | grep "mysql.*-community.*"
# 由于默认指定最新的版本(MySQL 8.0), 因此需要手动指定我们想安装的版本 (MySQL 5.7)
# 查看MySQL Yum存储库中的所有子存储库及其状态
yum repolist all | grep mysql
# 禁止8.0系列的子存储库
yum-config-manager --disable mysql80-community
# 启用5.7系列的子存储库
yum-config-manager --enable mysql57-community
# 或在安装时指定仓库 --disablerepo="*" --enablerepo="mysql57-community"
# 安装MySQL
# 会自动安装依赖: mysql-community-client, mysql-community-common, mysql-community-libs, mysql-community-libs-compat
yum install mysql-community-server -y
# 启动mysql server
service mysqld start
# 查看mysql server状态
service mysqld status
# 设置开机自动启动
chkconfig mysqld on
# 查看初始密码
# mysql server初始化时会创建账号 'root'@'localhost', 默认密码存放在错误日志中
grep 'temporary password' /var/log/mysqld.log
# 修改账号密码
mysql -uroot -p
mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码需包含大小写,数字,字符';
# 创建新账号
mysql> GRANT ALL ON *.* TO yjx@'%' IDENTIFIED BY '复杂密码';
# 查看当前mysql编码
show variables like 'character%';
# 修改mysql server字符集
# 修改 /etc/my.cnf
# 设置
#
# [mysqld]
# character_set_server=utf8
#
# 设置完后重启mysqld
# 不使用service来关闭mysqld的方法
mysqladmin -uroot -p shutdown
mysql_secure_installation
是用于设置root密码, 移除匿名用户等MySQL 5.7 请不要运行
mysql_secure_installation
, 因为安装时已经默认执行过了.
开启多实例
关闭默认实例
# 取消开机启动
chkconfig mysqld off
# 关闭默认mysqld
service mysqld stop
!!! mysql 5.7 以下没有 --initialize-insecure
和 --initialize
, 因此初始化数据库必须使用 mysql_install_db --datadir=【数据目录】
分别配置实例配置
mkdir -p /data/mysql_3307
mkdir -p /data/mysql_3308
chown -R mysql:mysql /data/mysql_33*
# 一份简单的配置文件
# basedir 指的是mysqld的安装目录
cat > /data/mysql_3307/my.cnf <<\EOF
[mysqld]
user = mysql
port = 3307
socket = /data/mysql_3307/mysqld.sock
pid-file = /data/mysql_3307/mysqld.pid
datadir = /data/mysql_3307/data
basedir = /usr
bind-address = 0.0.0.0
character_set_server = utf8
symbolic-links = 0
log_error = /data/mysql_3307/error.log
slow_query_log = 1
slow_query_log_file = /data/mysql_3307/slow.log
long_query_time = 2
EOF
# 复制配置文件
sed "s/3307/3308/g" /data/mysql_3307/my.cnf > /data/mysql_3308/my.cnf
# 初始化数据库
# --initialize-insecure 生成无密码的root账号
# --initialize 生成带随机密码的root账号
# 注意参数顺序, 必须先指定 --defaults-file=配置文件, 否则会报错
mysqld --defaults-file=/data/mysql_3307/my.cnf --initialize-insecure
mysqld --defaults-file=/data/mysql_3308/my.cnf --initialize-insecure
# 启动实例
mysqld_safe --defaults-file=/data/mysql_3307/my.cnf &
mysqld_safe --defaults-file=/data/mysql_3308/my.cnf &
# 关闭实例
mysqladmin -S /data/mysql_3307/mysqld.sock shutdown
mysqladmin -S /data/mysql_3308/mysqld.sock shutdown
# 连接实例
mysql -uroot -p -S /data/mysql_3307/mysqld.sock
# # 注意, 默认只有 root@localhost, 需自行创建 root@'%' 账号才能远程登录
mysql -uroot -h192.168.190.100 -P 3308
使用mysqld_multi管理多实例
# 创建目录
mkdir -p /data/mysql_3307
mkdir -p /data/mysql_3308
chown -R mysql:mysql /data/mysql_33*
# 初始化数据库
# 建议初始化时还是读取配置文件来初始化好, 初始化完成后再将配置集中到 multi.cnf
mysqld --defaults-file=/data/mysql_3309/my.cnf --initialize-insecure
mysqld --defaults-file=/data/mysql_3310/my.cnf --initialize-insecure
#mysqld -u mysql --basedir=/usr --datadir=/data/mysql_3309/data --initialize-insecure
#mysqld -u mysql --basedir=/usr --datadir=/data/mysql_3310/data --initialize-insecure
# 直接修改 /etc/my.cnf 或新创建一份multi配置 /data/multi.cnf
# 若是新创建multi配置, 则执行mysql_multi时需指定该配置文件
cat > /etc/multi.cnf <
!!!
mysqld_multi
不支持 !include 或 !includedir
eg. 采用 heartbeat + drbd + mysql 实现mysql高可用双机热备方案
未实践
主从复制
目的:
- 实时备份
- 读写分离, 主写, 从读
eg. MySQL 主从复制
简单原理
- Master的IO线程将操作记录到二进制日志(Binary log)中
- Slave的IO线程会同步拉取Master的二进制日志并写入本地中继日志(Relay log)
- Slave的SQL线程会从中继日志中读取操作并在当前实例上重放操作.
配置及操作
Master 配置文件
log_bin = /data/3306/mysql-bin
server-id = 1
#expire_logs_days = 10
#max_binlog_size = 100M
必须打开 master 端的 Binary Log
Slave 配置文件
read-only = 1
log_bin = /data/3307/mysql-bin
server-id = 2
# 可选, 指定中继日志的位置和命名
relay_log = /data/3307/relay.log
# 允许备库将其重放的事件也记录到自身的二进制日志中
log_slave_updates = 1
1. 确认Master 开启了二进制日志
mysql> SHOW MASTER STATUS;
Tip. 锁表
FLUSH TABLE WITH READ LOCK; UNLOCK TABLES;
2. Master 创建专门用于主从复制的账号
GRANT REPLICATION SLAVE,REPLICATION CLIENT ON *.* TO 'rep'@'192.168.100.%' IDENTIFIED BY '123456';
复制账号在主库上只需要REPLICATION SLAVE
权限,1.用来监控和管理复制的账号需要REPLICATION CLIENT 权限,并且针对这两种目的使用同一个账号更加容易(而不是为了两个目的各创建一个账号)。
2.如果在主库上建立了账号,然后从主库将数据克隆到备库上时,备库也就设置好了-变成主库所需要的配置。这样后续有需要可以方便的交换主备库角色。
3. 主-从 数据保持一致
# 主库导出数据快照
# 若表全都使用InnoDB引擎, 则可使用 --single-transaction 来代替 --lock-all-tables
# --master-data=1 导出sql中会包含CHANGE MASTER语句
# eg. CHANGE MASTER TO MASTER_LOG_FILE='bin.000003', MASTER_LOG_POS=25239;
# --master-data=2 导出CHANGE MASTER语句,但是会被注释(仅在平时备份时导出用)
mysqldump -uroot -p -S /data/3306/mysql.sock -A --master-data=1 --lock-all-tables > master.sql > master.sql
# 从库导入
mysql -uroot -p -S /data/3307/mysql.sock < master.sql
此处假设主数据库已经在使用, 而从数据库是新的, 因此需要先保持两边数据一致
4. Slave 更改从库的连接参数
# 尝试在此处不设置 MASTER_LOG_FILE,MASTER_LOG_POS, 结果后面 START SLAVE 后一直出错
# 此处的 MASTER_LOG_FILE,MASTER_LOG_POS 可以在日志中查看
mysql> CHANGE MASTER TO MASTER_HOST='192.168.190.100',MASTER_PORT=3309,MASTER_USER='rep',MASTER_PASSWORD='123456',MASTER_LOG_FILE='bin.000003', MASTER_LOG_POS=25239;
# 确认一下配置文件正确
cat /data/3307/data/master.info
# 从库连接主库
mysql> START SLAVE;
# 确认连接正常
mysql> SHOW SLAVE STATUS\G;
管理
-- 查看master的状态, 尤其是当前的日志及位置
show master status;
-- 查看slave的状态.
show slave status;
-- 重置slave状态,用于删除SLAVE数据库的relaylog日志文件,并重新启用新的relaylog文件.会忘记 主从关系,它删除master.info文件和relay-log.info 文件
reset slave;
-- 启动slave 状态(开始监听msater的变化)
start slave;
-- 暂停slave状态;
stop slave;
-- 跳过导致复制终止的n个事件,仅在slave线程没运行的状况下使用
set global sql_slave_skip_counter = n;
关键参数配置
以下参数中, 部分参数只对InnoDB引擎有效
参数名 | 含义 | 建议 |
---|---|---|
max_connections |
最大客户端连接数 | |
innodb_buffer_pool_size |
Innodb存储引擎缓存池大小 | 建议设置为物理内存的 80% 左右 |
innodb_file_per_table |
Innodb中每个表存放使用独立的表空间 否则是放在共享空间 |
设为 1, 表示每个表使用独立表空间, 方便回收空间. |
innodb_log_file_size |
事务日志(Redo Log)单个大小(文件 ib_logfile* |
总的日志大小足以容纳1个小时的量 |
innodb_log_files_in_group |
事务日志数量 | 默认是 2 |
innodb_flush_logs_at_trx_commit |
事务提交时写日志的方式 0: 每秒将日志持久化到磁盘. 数据库崩溃时丢失最多1秒数据 1: 默认, 每次事务提交都将日志持久化到磁盘, 最安全, 性能最一般. 2: 每次事务提交都写入磁盘(指磁盘缓冲区), 具体何时持久化到磁盘则由操作系统控制. 系统崩溃时丢失数据 |
不推荐设为 0. 对性能要求较高可以设置为 2. |
sync_binlog |
MySQL 控制写入 BinLog 的方式 0 : 每次事务提交写入磁盘缓冲区, 由操作系统控制何时持久化 N: 每进行N个事务提交后持久化到磁盘, 当N=1时最安全但性能最差 5.7.7及以后默认是1, 之前默认是0 |
|
mysql 自带 mysqlslap 压测工具, 可以自行测试, 个人未使用过.
计算合适的 Redo Log 大小
调整Redo Log大小一般是通过修改 innodb_log_file_size
配置.
-
在业务高峰期, 计算出1分钟写入的redo log量
# 只显示结果中 Log 相关 > pager grep Log; # 查看日志位置, sleep > show engine innodb status\G; select sleep(60); show engine innodb status\G; # 取消结果过滤 > nopager;
通过查看两次的
Log sequence number
值, 计算出差值, 单位是字节. - 评估1个小时的 redo log 量
将上述值乘以 60 得到合理的 Redo Log 大小. 因此
innodb_log_file_size
推荐设置成 估算的Redo Log 大小 / 日志文件数(innodb_log_files_in_group
) - 正常关闭 mysql
- 修改配置中
innodb_log_file_size
值, 并将数据目录下的所有ib_logfile*
文件move走(先不删, 防止出问题无法启动) - 启动 mysql, 确认没问题后就可以删除
ib_logfile*
备注: 有看到说 mysql5.6 版本及以后无需手动删除
ib_logfile*
文件.
如果Redo Log太小, 会导致频繁刷脏页??
太大会导致故障恢复时恢复时间太长(甚至长到不可接受的程度)
基本使用
USE 数据库名; -- 选择数据库
SHOW DATABASES; -- 查看数据库列表
SHOW TABLES; -- 查看当前数据库内的表的列表
SHOW COLUMNS FROM `表名`; -- 查看表结构
SHOW STATUS; -- 用于显示广泛的服务器状态信息
SHOW CREATE DATABASE `数据库名`; -- 显示创建特定数据库的MySQL语句
SHOW CREATE TABLE `表名`; -- 显示创建表的MySQL语句;
SHOW GRANTS; -- 显示授权指定用户的安全权限, 默认是当前用户
SHOW GRANTS FOR 用户@"..." -- eg. root@'%' 或 root@localhost 或 [email protected]
SHOW ERRORS -- 用来显示服务器错误消息
SHOW WARNINGS -- 用来显示服务器警告消息
-
DESCRIBE 表名
等同于
SHOW COLUMNS FROM 表名
, MySQL独有 -
STATUS
快速查看当前实例状态, eg.
-------------- mysql Ver 14.14 Distrib 5.7.22, for Linux (x86_64) using EditLine wrapper Connection id: 28 Current database: mysql_learn Current user: root@localhost SSL: Not in use Current pager: stdout Using outfile: '' Using delimiter: ; Server version: 5.7.22-0ubuntu18.04.1 (Ubuntu) Protocol version: 10 Connection: 127.0.0.1 via TCP/IP Insert id: 114 Server characterset: latin1 Db characterset: latin1 Client characterset: utf8 Conn. characterset: utf8 TCP port: 3306 Uptime: 7 days 23 hours 29 min 13 sec Threads: 6 Questions: 817 Slow queries: 0 Opens: 205 Flush tables: 1 Open tables: 150 Queries per second avg: 0.001 --------------
数据类型
数值数据类型
有符号或无符号所有数值数据类型(除 BIT 和 BOOLEAN 外)都可以有符号或无符号。有符号数值列可以存储正或负的数值,无符号数值列只能存储正数。默认情况为有符号,但如果你知道自己不需要存储负值,可以使用 UNSIGNED 关键字,这样做将允许你存储两倍大小的值。
类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 |
---|---|---|---|---|
TINYINT | 1 字节 | (-128,127) | (0,255) | 小整数值 |
SMALLINT | 2 字节 | (-32 768,32 767) | (0,65 535) | 大整数值 |
MEDIUMINT | 3 字节 | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 |
INT或INTEGER | 4 字节 | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 |
BIGINT | 8 字节 | (-9,223,372,036,854,775,808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 |
FLOAT | 4 字节 | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 |
DOUBLE | 8 字节 | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 |
DECIMAL | DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 |
关键字INT是INTEGER的同义词,关键字DEC是DECIMAL的同义词。
int(m) 里的m是表示 SELECT 查询结果集中的显示宽度,并不影响实际的取值范围.
FLOAT 类型
float(m,d)
DOUBLE 类型
double(m,d)
DECIMAL 类型
- 精确值
decimal(m,d)
m<=65, d<=30, m是总位数, d是小数位数
字符串数据类型
类型 | 大小 | 用途 |
---|---|---|
CHAR | 0-255字节 | 定长字符串 |
VARCHAR | 0-65535 字节 | 变长字符串 |
TINYBLOB | 0-255字节 | 不超过 255 个字符的二进制字符串 |
TINYTEXT | 0-255字节 | 短文本字符串 |
BLOB | 0-65 535字节 | 二进制形式的长文本数据 |
TEXT | 0-65 535字节 | 长文本数据 |
MEDIUMBLOB | 0-16 777 215字节 | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215字节 | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295字节 | 二进制形式的极大文本数据 |
LONGTEXT | 0-4 294 967 295字节 | 极大文本数据 |
CHAR 类型
char(n) 若存入字符数小于n,则以空格补于其后,查询之时再将空格去掉。所以 char 类型存储的字符串末尾不能有空格
- 效率比 VARCHAR 高点
VARCHAR 类型
- 长度设置, 在MySQL 5之后是按字符数, 而不是字节数.
varchar(20)
可以存储20个字符- varchar 头部会占用 1个(n<=255)或2个字节(n>255)保存字符串长度, 若值可设为 null, 则还需要一个1字节记录null, 因此保存utf8编码的字符串最多可存 (65535 - 3)/3 = 21844
- 若是utf8编码
- 效率比 TEXT 高
TEXT 类型
- 创建索引时要指定前多少个字符
- 不能有默认值
- text 头部会占用 2个字节来保存长度
日期和时间类型
类型 | 大小 (字节) | 范围 | 格式 | 用途 |
---|---|---|---|---|
DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 |
TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 |
YEAR | 1 | 1901/2155 | YYYY | 年份值 |
DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 |
TIMESTAMP | 4 | 1970-01-01 00:00:00/2038结束时间是第 2147483647 秒,北京时间 2038-1-19 11:14:07,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 |
每个时间类型有一个有效值范围和一个"零"值,当指定不合法的MySQL不能表示的值时使用"零"值。
TIMESTAMP类型有专有的自动更新特性
update_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
- 保存毫秒数(类似php 的
microtime(true)
)- timestamp列默认not null。没有显式指定nullable,那么default null不合法
- mysql不会给timestamp设置默认值,除非显式设置default约束或者可空null。特例:mysql会给表第一个timestamp类型的字段同时添加
default current_timestamp
和on update timestamp
- 其他情况均会引起不合法报错
- ↑ 总结: 最好手动设置 NULL 以免出错
日期函数:
-
CURRENT_TIMESTAMP
,CURRENT_TIMESTAMP()
是NOW()
的同义词 -
SYSDATE()
获取的是函数执行时的时间,NOW()
获取的是执行语句时的开始时间.
二进制数据类型
检索数据
SELECT 语句
子句的书写顺序很重要:
SELECT ... FROM ... JOIN ... ON ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ...
执行顺序:
FROM > WHERE(包含JOIN) > GROUP BY > 聚集函数字段计算 > HAVING > SELECT 的字段 > ORDER BY > LIMIT
举例:
SELECT player.team_id, count(*) as num -- 顺序5
FROM player JOIN team ON player.team_id = team.team_id -- 顺序1
WHERE height > 1.80 -- 顺序2
GROUP BY player.team_id -- 顺序3
HAVING num > 2 -- 顺序4
ORDER BY num DESC -- 顺序6
LIMIT 2; -- 顺序7
基本检索
SELECT `列名1`,`列名2` FROM `表名`; -- 检索指定列
SELECT * FROM `表名`; -- 检索所有列
DISTINCT关键字: 只返回不同的值
SELECT DISTINCT `列名1` FROM `表名`; -- 只返回唯一的 `列名1`行
SELECT DISTINCT `列名1`,`列名2` FROM `表名`; -- DISTINCT应用于所有的列, 返回 (`列名1`, `列名2`) 不同的行
LIMIT关键字: 限制结果
SELECT * FROM `表名` LIMIT ; -- 限制返回最多 条, 等同 0,
SELECT * FROM `表名` LIMIT ,; -- 略过前 条记录, 返回最多 条
SELECT * FROM `表名` LIMIT OFFSET ; -- MySQL 5 语法糖
完全限定名
SELECT `表名`.`列名` FROM `数据库名`.`表名`; -- 有一些情形需要完全限定名
ORDER 子句
SELECT * FROM `表名` ORDER BY `列名` ; -- 默认是 ASC, 可指定 DESC.
SELECT * FROM `表名` ORDER BY `列名1` ,`列名2` ; -- 仅在`列名1`等值时才按`列名2`排序
排序方式可选:
- ASC 升序(默认), ASCENDING
- DESC 降序
- ORDER BY 字句使用的排序列不一定是显示的列, 这是允许的.
- ORDER BY
- 对文本数据排序时, 大小写的排序取决于数据库如何设置
COLLATE
WHERE 子句
操作符 | 说明 |
---|---|
= |
等于 |
<> |
不等于 |
!= |
不等于 |
< |
小于 |
<= |
小于等于 |
> |
大于 |
>= |
大于等于 |
BETWEEN ... AND ... |
在指定的两个值之间 |
... IS NULL |
没有值 |
... IS NOT NULL |
非空, 有值 |
IN (…) | 在元组 |
NOT IN (...) | 不在元组 |
- 一个列不包含值时, 称其为空值NULL
AND, OR 操作符
计算次序: AND 运算符优先级更高, 在处理OR操作符前会先处理AND操作符, 举例:
SELECT prod_name,prod_price,vend_id FROM products WHERE vend_id=1002 OR vend_id=1003 AND prod_price>=10;
-- 等价于
SELECT prod_name,prod_price,vend_id FROM products WHERE (vend_id=1002) OR (vend_id=1003 AND prod_price>=10);
IN操作符
SELECT * FROM `表名` WHERE `列名` IN (值1, 值2, ..., 值N);
IN
功能等同于 OR
, 那么为什么用 IN
:
- 语法清晰直观
- 执行更快
- 可以包含其他SELECT 语句, 得能够更动态地建立 WHERE 子句
NOT操作符
否定它之后所跟的任何条件
... WHERE `列名` NOT IN (值1, ..., 值N);
... WHERE `列名` IS NOT NULL;
... WHERE `列名` NOT BETWEEN 1 AND 10;
... WHERE `列名` NOT EXISTS (...);
NOT 在复杂的WHERE字句中很有用.
例如,在与 IN 操作符联合使用时, NOT 使找出与条件列表不匹配的行非常简单。MySQL支持使用 NOT 对 IN 、 BETWEEN 和
EXISTS子句取反,这与多数其他 DBMS允许使用 NOT 对各种条件
取反有很大的差别。
LIKE操作符
通配符(wildcard)
通配符 | 含义 |
---|---|
% |
任何字符出现任意次数(0,1,N), 注意不匹配 NULL |
_ |
匹配单个字符(1) |
like 匹配完整的列.
通配符置于搜索模式开始处, 不会使用索引.
注意NULL 虽然似乎 % 通配符可以匹配任何东西,但有一个例外,即 NULL 。即使是 WHERE prod_name LIKE '%' 也不能匹配用值 NULL 作为产品名的行。
SELECT * FROM `表名` WHRER `列名` LIKE `a%d`;
SELECT * FROM `表名` WHRER `列名` LIKE `a_cd`;
REGEXP 正则表达式
MySQL仅支持多数正则表达式实现的一个很小的子集。
eg. 不支持\d
,\s
等
SELECT * FROM `表名` WHERE `列名` REGEXP '^[0-9]'; -- 匹配数字开头的, 默认不区分大小写
SELECT * FROM `表名` WHERE `列名` REGEXP BINARY "正则表达式"; -- 区分大小写
多数正则表达式实现使用单个反斜杠转义特殊字符,以便能使用这些字符本身。但MySQL要求两个反斜杠(MySQL自己解释一个,正则表达式库解释另一个)。
转义.
时, 需使用\\.
而非\.
计算字段
计算字段并不实际存在于数据库表中。计算字段是运行时在 SELECT 语句内创建的。
字段(field) 基本上与列(column)的意思相同,经常互换使用,不过数据库列一般称为列,而术语字段通常用在计算字段的连接上。
AS 别名
别名(alias)是一个字段或值的替换名。
SELECT `列名` AS `别名` FROM `表名`;
别名的其他常见用途:
- 在实际的表列名包含不符合规定的字符(如空格)时重新命名它
- 在原来的名字含混或容易误解时扩充它,等等。
算数运算
圆括号可用来区分优先顺序。
函数
函数没有SQL的可移植性强, 几乎每种主要的DBMS的实现都支持其他实现不支持的函数,而且有时差异还很大。
字符串处理
concat()
字符串拼接
SELECT concat(`列名1`, '(', `列名2`, ')') FROM `表名`; -- 组成: `列名1`(`列名2`)
MySQL的不同之处 多数DBMS使用 + 或 || 来实现拼接,MySQL则使用 Concat() 函数来实现。当把SQL语句转换成MySQL语句时一定要把这个区别铭记在心
-
LOCATE(substr,str)
返回子串 substr 在字符串 str 中第一次出现的位置。如果子串 substr 在 str 中不存在,返回值为 0:
-
LOCATE(substr,str,pos)
多字节安全
返回子串 substr 在字符串 str 中的第 pos 位置后第一次出现的位置。如果 substr 不在 str 中返回 0
-
substr(str,pos)
pos
为1时表示截取全部 substr(str,pos,len)
-
concat_ws(separator, str1, str2, ...)
使用指定分隔符拼接参数, 忽略NULL
-
group_concat(...)
函数返回一个字符串结果,该结果由分组中的值连接组合而成。
!!! 注意 MySQL字符串位置是从1开始
日期和时间
MySQL 日期格式固定为: yyyy-mm-dd
, 无论是插入,更新,或WHERE字句过滤.
补充
函数 | 说明 | 示例 |
---|---|---|
from_unixtime() |
将时间戳转为日期 | from_unixtime(1559001600) |
unix_timestamp() |
将指定日期或日期字符串转换为时间戳 | unix_timestamp(Now()) |
数值处理
聚集函数
标准偏差 MySQL还支持一系列的标准偏差聚集函数
-
AVG()
,MAX()
,MIN()
,SUM()
函数忽略列值为 NULL 的行 -
COUNT(*)
对表中行的数目进行计数,不管表列中包含的是空值( NULL )还是非空值 -
COUNT(column)
对特定列中具有值的行进行计数,忽略NULL 值
上述聚集函数可配合 DISTINCT 来使用
SELECT COUNT(DISTINCT `列名`) FROM `表名`;
SELECT AVG(DISTINCT `列名`) FROM `表名`;
SELECT SUM(DISTINCT `列名`) FROM `表名`;
其他函数-待整理
CHAR_LENGTH(str)
返回值为字符串str 的长度,长度的单位为字符。一个多字节字符算作一个单字符。
对于一个包含五个二字节字符集, LENGTH()返回值为 10, 而CHAR_LENGTH()的返回值为5。
CONCAT(str1,str2,...)
字符串拼接
如有任何一个参数为NULL ,则返回值为 NULL。
CONCAT_WS(separator,str1,str2,...)
字符串拼接(自定义连接符)
CONCAT_WS()不会忽略任何空字符串。 (然而会忽略所有的 NULL)。
CONV(N,from_base,to_base)
进制转换
例如:
SELECT CONV('a',16,2); 表示将 a 由16进制转换为2进制字符串表示
FORMAT(X,D)
将数字X 的格式写为'#,###,###.##',以四舍五入的方式保留小数点后 D 位, 并将结果以字符串的形式返回。若 D 为 0, 则返回结果不带有小数点,或不含小数部分。
例如:
SELECT FORMAT(12332.1,4); 结果为: '12,332.1000'
INSERT(str,pos,len,newstr)
在str的指定位置插入字符串
pos:要替换位置其实位置
len:替换的长度
newstr:新字符串
特别的:
如果pos超过原字符串长度,则返回原字符串
如果len超过原字符串长度,则由新字符串完全替换
INSTR(str,substr)
返回字符串 str 中子字符串的第一个出现位置。
LEFT(str,len)
返回字符串str 从开始的len位置的子序列字符。
LOWER(str)
变小写
UPPER(str)
变大写
LTRIM(str)
返回字符串 str ,其引导空格字符被删除。
RTRIM(str)
返回字符串 str ,结尾空格字符被删去。
SUBSTRING(str,pos,len)
获取字符串子序列
LOCATE(substr,str,pos)
获取子序列索引位置
REPEAT(str,count)
返回一个由重复的字符串str 组成的字符串,字符串str的数目等于count 。
若 count <= 0,则返回一个空字符串。
若str 或 count 为 NULL,则返回 NULL 。
REPLACE(str,from_str,to_str)
返回字符串str 以及所有被字符串to_str替代的字符串from_str 。
REVERSE(str)
返回字符串 str ,顺序和字符顺序相反。
RIGHT(str,len)
从字符串str 开始,返回从后边开始len个字符组成的子序列
SPACE(N)
返回一个由N空格组成的字符串。
SUBSTRING(str,pos) , SUBSTRING(str FROM pos) SUBSTRING(str,pos,len) , SUBSTRING(str FROM pos FOR len)
不带有len 参数的格式从字符串str返回一个子字符串,起始于位置 pos。带有len参数的格式从字符串str返回一个长度同len字符相同的子字符串,起始于位置 pos。 使用 FROM的格式为标准 SQL 语法。也可能对pos使用一个负值。假若这样,则子字符串的位置起始于字符串结尾的pos 字符,而不是字符串的开头位置。在以下格式的函数中可以对pos 使用一个负值。
mysql> SELECT SUBSTRING('Quadratically',5);
-> 'ratically'
mysql> SELECT SUBSTRING('foobarbar' FROM 4);
-> 'barbar'
mysql> SELECT SUBSTRING('Quadratically',5,6);
-> 'ratica'
mysql> SELECT SUBSTRING('Sakila', -3);
-> 'ila'
mysql> SELECT SUBSTRING('Sakila', -5, 3);
-> 'aki'
mysql> SELECT SUBSTRING('Sakila' FROM -4 FOR 2);
-> 'ki'
TRIM([{BOTH | LEADING | TRAILING} [remstr] FROM] str) TRIM(remstr FROM] str)
返回字符串 str , 其中所有remstr 前缀和/或后缀都已被删除。若分类符BOTH、LEADIN或TRAILING中没有一个是给定的,则假设为BOTH 。 remstr 为可选项,在未指定情况下,可删除空格。
mysql> SELECT TRIM(' bar ');
-> 'bar'
mysql> SELECT TRIM(LEADING 'x' FROM 'xxxbarxxx');
-> 'barxxx'
mysql> SELECT TRIM(BOTH 'x' FROM 'xxxbarxxx');
-> 'bar'
mysql> SELECT TRIM(TRAILING 'xyz' FROM 'barxxyz');
-> 'barx'
分组
GROUP BY ... HAVING ...
创建分组
聚集 配合 分组 GROUP BY 子句指示MySQL分组数据,然后对每个组而不是整个结果集进行聚集。
使用 GROUP BY
的重要规定
- ??? GROUP BY 子句可以包含任意数目的列。这使得能对分组进行嵌套,为数据分组提供更细致的控制。
- ??? 如果在 GROUP BY 子句中嵌套了分组,数据将在最后规定的分组上进行汇总。换句话说,在建立分组时,指定的所有列都一起计算(所以不能从个别的列取回数据)。
- GROUP BY 子句中列出的每个列都必须是检索列或有效的表达式(但不能是聚集函数)。如果在 SELECT 中使用表达式,则必须在GROUP BY 子句中指定相同的表达式。不能使用别名。
- 除聚集计算语句外, SELECT 语句中的每个列都必须在 GROUP BY 子句中给出。
- 如果分组列中具有 NULL 值,则 NULL 将作为一个分组返回。如果列中有多行 NULL 值,它们将分为一组。
- GROUP BY 子句必须出现在 WHERE 子句之后, ORDER BY 子句之前。
??
过滤分组
HAVING
过滤的是分组
WHERE
过滤的是行
-
HAVING
可以使用别名
HAVING 和 WHERE 的差别
这里有另一种理解方法, WHERE 在数据分组前进行过滤, HAVING 在数据分组后进行过滤。这是一个重要的区别, WHERE 排除的行不包括在分组中。这可能会改变计算值,从而影响 HAVING 子句中基于这些值过滤掉的分组。
子查询
根据子查询执行的次数, 对子查询进行分类:
- 非关联子查询
查询不依赖外层(即与主查询无关), 该子查询只需执行一次, 得到的数据结果可以作为外层查询的条件直接使用.
- 关联子查询
查询时依赖外层(用到外层表数据, 与主查询相关), 因此每次外层查询都要根据外层查询结果再进行子查询, 该子查询需要执行多次.
在实际使用时由于性能的限制,不能嵌套太多的子查询。
eg. 返回订购产品 TNT2 的客户列表
select * from customers
where cust_id in (select distinct cust_id
from orders
where order_num in (select order_num
from orderitems
where prod_id="TNT2")
);
子查询的关键字
存在性子查询
- EXISTS
判断条件是否满足, 满足为True, 否则为False
集合比较子查询
- IN
判断是否在集合中
-
ANY
必须与比较操作符一起使用, 与子查询中任意值比较
SELECT * FROM A WHERE A.cc > ANY (SELECT cc FROM B);
- SOME
是ANY的别名, 等价于 ANY, 一般常用 ANY
-
ALL
必须与比较操作符一起使用, 与子查询中所有值比较
SELECT * FROM A WHERE A.cc > ALL (SELECT cc FROM B);
Q. 对于 表 A, B 的子查询, EXISTS 和 IN 要选哪个?
A. 需要根据表A 和 表B 的表大小及索引情况而定.
通常使用 IN 及 EXISTS 情况可以概括为以下 SQL 语句:
-- 语句1
SELECT * FROM A WHERE cc IN (SELECT cc FROM B);
-- 语句2
SELECT * FROM A WHERE EXISTS (SELECT cc FROM B WHERE A.cc = B.cc);
原则: 小表驱动大表.
结论:
- 如果 B表 比较小, 且 A表在 cc 列上有索引, 则推荐使用语句1.
这里的表大小指的是根据where筛选后的大小, 而非表的原始大小
- 如果 A表 比较小, 且 B表在 cc 列上有索引, 则推荐使用语句2.
表联结/连接 Join
联结是一种机制,用来在一条 SELECT语句中关联表,因此称之为联结。
使用特殊的语法,可以联结多个表返回一组输出,联结在运行时关联表中正确的行。
使用联结的要点:
- 注意所使用的联结类型。一般我们使用内部联结,但使用外部联结也是有效的。
- 保证使用正确的联结条件,否则将返回不正确的数据。
- 应该总是提供联结条件,否则会得出笛卡儿积。
- 在一个联结中可以包含多个表,甚至对于每个联结可以采用不同的联结类型。虽然这样做是合法的,一般也很有用,但应该在一起测试它们前,分别测试每个联结。这将使故障排除更为简单。
表别名只在查询执行中使用。与列别名不一样,表别名不返回到客户机。
完全限定列名 在引用的列可能出现二义性时,必须使用完全限定列名(用一个点分隔的表名和列名)。如果引用一个没有用表名限制的具有二义性的列名,MySQL将返回错误。
在联结两个表时,你实际上做的是将第一个表中的每一行与第二个表中的每一行配对。 WHERE 子句作为过滤条件,它只包含那些匹配给定条件(这里是联结条件)的行。没有WHERE 子句,第一个表中的每个行将与第二个表中的每个行配对,而不管它们逻辑上是否可以配在一起。
示例代码↓
-- 将表 vendors 与表 products 联结, 联结条件是: vendors.vend_id = products.vend_id
SELECT prod_id,vend_name,prod_name,prod_price FROM vendors,products WHERE vendors.vend_id = products.vend_id;
-- 表 vendors 每一行都将于表 products 每一行配对
-- 此时返回的结果为 笛卡尔积, 返回行数目是: count(表A) * count(表B)
SELECT prod_id,vend_name,prod_name,prod_price FROM vendors,products;
根据获取到的结果集的范围将连接进行分类:
- 内连接: 取多个表之间满足条件的数据行(交集)
隐式的内连接: 不写
INNER JOIN
显式的内连接: 写
INNER JOIN
- 外连接: 除了取满足条件的交集外, 还会取某一方不满足条件的记录.
左外连接 LEFT OUTER JOIN
右外连接 RIGHT OUTER JOIN
全外连接 FULL OUTER JOIN
书写时
OUTER
可以忽略 - 交叉连接: 笛卡尔积(cartesian product)(所有集合的所有组合).
CROSS JOIN
返回记录的条数是每个表的行数的乘积.
根据连接时的测试条件, 将连接进行分类:
- 等值连接: 连接条件是等号
- 非等值连接: 连接条件是非等号
当自身与自身进行连接时, 称为自连接.
内部联结 INNER JOIN
内部联结即上面的等值联结, 它基于两个表之间的相等测试。
SELECT vendors.vend_id,vend_name,prod_name,prod_price FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id;
使用哪种语法? ANSI SQL规范首选 INNER JOIN 语法。此外,尽管使用 WHERE 子句定义联结的确比较简单,但是使用明确的联结语法能够确保不会忘记联结条件,有时候这样做也能影响性能。
性能考虑 MySQL在运行时关联指定的每个表以处理联结。这种处理可能是非常耗费资源的,因此应该仔细,不要联结不必要的表。联结的表越多,性能下降越厉害。
eg. 返回订购产品 TNT2 的客户列表
-- 子查询方式
select * from customers
where cust_id in (select distinct cust_id
from orders
where order_num in (select order_num
from orderitems
where prod_id="TNT2")
);
-- 表联结方式1
SELECT cust_name,cust_contact FROM customers,orders,orderitems WHERE customers.cust_id = orders.cust_id AND orders.order_num = orderitems.order_num AND orderitems.prod_id = 'TNT2';
-- 表联结方式2
SELECT cust_name,cust_contact FROM customers INNER JOIN orders ON customers.cust_id = orders.cust_id INNER JOIN orderitems ON orders.order_num = orderitems.order_num WHERE orderitems.prod_id = "TNT2";
自联结
Eg. 假如你发现某物品(其ID为 DTNTR )存在问题,因此想知道生产该物品的供应商生产的其他物品是否也存在这些问题。
-- 子查询方式
SELECT prod_id,prod_name
FROM products
WHERE vend_id = (
SELECT vend_id
FROM products
WHERE prod_id = 'DTNTR'
);
-- 自联结方式
SELECT p1.prod_id,p1.prod_name
FROM products as p1,products as p2
WHERE p1.vend_id = p2.vend_id AND p2.prod_id = 'DTNTR';
用自联结而不用子查询 自联结通常作为外部语句用来替代从相同表中检索数据时使用的子查询语句。虽然最终的结果是相同的,但有时候处理联结远比处理子查询快得多。应该试一下两种方法,以确定哪一种的性能更好
外部联结 OUTER JOIN
外部联结: 联结包含了那些在相关表中没有关联行的行。
许多联结将一个表中的行与另一个表中的行相关联。但有时候会需
要包含没有关联行的那些行。例如,可能需要使用联结来完成以下工作:
- 对每个客户下了多少订单进行计数,包括那些至今尚未下订单的
客户;- 列出所有产品以及订购数量,包括没有人订购的产品;
- 计算平均销售规模,包括那些至今尚未下订单的客户。
-- 使用 LEFT OUTER JOIN 从 FROM子句的左边表( customers 表)中选择所有行
select c.cust_id,o.order_num
from customers as c
left outer join orders as o
on c.cust_id = o.cust_id;
-- 查看所有客户的订单数量(聚集函数), 包含从没下过单的客户
SELECT c.cust_id,cust_name,count(distinct o.order_num)
FROM customers as c
LEFT OUTER JOIN orders as o
ON c.cust_id=o.cust_id
GROUP BY c.cust_id;
与内部联结关联两个表中的行不同的是,外部联结还包括没有关联行的行。在使用 OUTER JOIN 语法时,必须使用 RIGHT 或 LEFT 关键字指定包括其所有行的表( RIGHT 指出的是 OUTER JOIN 右边的表,而 LEFT指出的是 OUTER JOIN 左边的表)。
- LEFT OUTER JOIN 左外联结, 包含坐标的全部记录, 若无对应的右边记录, 则其值为 NULL
- RIGHT OUTER JOIN
OUTER 关键字可以省略不写.
外部联结的类型 存在两种基本的外部联结形式:左外部联结和右外部联结。它们之间的唯一差别是所关联的表的顺序不同。换句话说,左外部联结可通过颠倒 FROM 或 WHERE 子句中表的顺序转换为右外部联结。因此,两种类型的外部联结可互换使用,而究竟使用哪一种纯粹是根据方便而定。
组合查询 UNION
MySQL也允许执行多个查询(多条 SELECT 语句),并将结果作为单个查询结果集返回。这些组合查询通常称为并(union)或复合查询(compound query)。
组合查询和多个 WHERE 条件 多数情况下,组合相同表的两个查询完成的工作与具有多个 WHERE 子句条件的单条查询完成的工作相同。
-- 返回查询(过滤重复行)
SELECT ... FROM ...
UNION
SELECT ... FROM ...
ORDER BY ...
-- 返回查询(保留所有行)
SELECT ... FROM ...
UNION ALL
SELECT ... FROM ...
ORDER BY ...
对于更复杂的过滤条件,或者从多个表(而不是单个表)中检索数据的情形,使用 UNION 可能会使处理更简单。
UNION规则
- UNION 必须由两条或两条以上的 SELECT 语句组成,语句之间用关键字 UNION 分隔(因此,如果组合4条 SELECT 语句,将要使用3个UNION 关键字)。
- UNION 中的每个查询必须包含相同的列、表达式或聚集函数(不过各个列不需要以相同的次序列出)。
- 列数据类型必须兼容:类型不必完全相同,但必须是DBMS可以隐含地转换的类型(例如,不同的数值类型或不同的日期类型)。
- 组合查询可以引用于不同的表
特点
- UNION 默认会去除重复的行, 如果要返回所有匹配行, 则要使用 UNION ALL
- UNION查询只能使用一条ORDER BY 子句, 只能出现在最后一条 SELECT 语句之后.
全文本搜索
重要说明
- 在索引全文本数据时,短词被忽略且从索引中排除。短词定义为那些具有3个或3个以下字符的词(如果需要,这个数目可以更改)。
- MySQL带有一个内建的非用词(stopword)列表,这些词在索引全文本数据时总是被忽略。如果需要,可以覆盖这个列表(请参阅MySQL文档以了解如何完成此工作)。
- 许多词出现的频率很高,搜索它们没有用处(返回太多的结果)。因此,MySQL规定了一条50%规则,如果一个词出现在50%以上的行中,则将它作为一个非用词忽略。50%规则不用于 IN BOOLEANMODE 。
- 如果表中的行数少于3行,则全文本搜索不返回结果(因为每个词或者不出现,或者至少出现在50%的行中)。
- 忽略词中的单引号。例如, don't 索引为 dont 。
- 不具有词分隔符(包括日语和汉语)的语言不能恰当地返回全文本搜索结果。
- 如前所述,仅在 MyISAM 数据库引擎中支持全文本搜索。
启用全文本搜索支持 FULLTEXT
MyISAM 引擎支持, InnoDB 引擎不支持.
为了进行全文本搜索,必须索引被搜索的列
CREATE TABLE table_name(
note_id int NOT NULL AUTO_INCREMENT,
note_text text NULL,
PRIMARY KEY (note_id),
FULLTEXT(note_text), -- 创建全文本索引
) ENGINE=MyISAM;
!!不要在导入数据时使用 FULLTEXT更新索引要花时间,虽然不是很多,但毕竟要花时间。如果正在导入数据到一个新表,此时不应该启用 FULLTEXT 索引。应该首先导入所有数据,然后再修改表,定义 FULLTEXT 。这样有助于更快地导入数据(而且使索引数据的总时间小于在导入每行时分别进行索引所需的总时间)。
进行全文本搜索 MATCH…AGAINST…
全文本搜索返回的结果默认排序是按照关联程度最高的排在最前面
-- 针对指定的列进行搜索
SELECT * FROM `表名` WHERE Match(`列名`) Against('搜索词');
Match(
列名) Against('搜索词')
实际上是计算出一个代表关联程度的数值, 该数值可以在 SELECT 中直接查看.
?? 使用完整的 Match() 说明 传递给 Match() 的值必须与FULLTEXT() 定义中的相同。如果指定多个列,则必须列出它们(而且次序正确)。
!!搜索不区分大小写 除非使用 BINARY 方式(本章中没有介绍),否则全文本搜索不区分大小写。
查询扩展 WITH EXPANSION
-- WITH QUERY EXPANSION 使用查询扩展
SELECT note_id,note_text
FROM productnotes
WHERE match(note_text) against('anvils' WITH QUERY EXPANSION);
MySQL对数据和索引进行两遍扫描来完成搜索:
- 首先,进行一个基本的全文本搜索,找出与搜索条件匹配的所有行;
- 其次,MySQL检查这些匹配行并选择所有有用的词
- 再其次,MySQL再次进行全文本搜索,这次不仅使用原来的条件,而且还使用所有有用的词。
利用查询扩展,能找出可能相关的结果,即使它们并不精确包含所查找的词。
行越多越好 表中的行越多(这些行中的文本就越多),使用查询扩展返回的结果越好。
布尔文本搜索
布尔方式(booleanmode)
- 要匹配的词;
- 要排斥的词(如果某行包含这个词,则不返回该行,即使它包含
其他指定的词也是如此); - 排列提示(指定某些词比其他词更重要,更重要的词等级更高);
- 表达式分组;
- 另外一些内容。
即使没有 FULLTEXT 索引也可以使用 布尔方式不同于迄今为止使用的全文本搜索语法的地方在于,即使没有定义
FULLTEXT 索引,也可以使用它。但这是一种非常缓慢的操作(其性能将随着数据量的增加而降低)。
SELECT note_id,note_text
FROM productnotes
WHERE match(note_text) against('heavy -rope*' IN BOOLEAN MODE);
说明:
- 匹配
heavy
- 排除
rope
开头的词
插入数据 INSERT INTO
几种使用方式
- 插入完整的行;
- 插入行的一部分;
- 插入多行;
- 插入某些查询的结果
插入时必须对每个列必须提供一个值.
-- 简单但不安全, 依赖表中列的定义次序
INSERT INTO customer VALUES(NULL,'pep', '100 main', 'los angles', 'CA', '90046', 'USA', NULL, NULL);
-- 指定插入的列, 推荐(但很繁琐)
INSERT INTO customers(cust_name,cust_address,cust_city,cust_state,cust_zip,cust_country,cust_contact,cust_email) VALUES('pep', '100 main', 'los angles', 'CA', '90046', 'USA', NULL, NULL);
-- 插入多行
INSERT INTO `表名`(`列名1`, `列名2`) VALUES("值1", "值2"),("值3", "值4"),("值5", "值6");
-- 插入检索出的数据, 注意避免主键的冲突
INSERT INTO `表1`(`列名1`, `列名2`) SELECT `列名1`, `列名2` FROM `表2`;
插入时省略列需满足以下任一条件:
- 该列定义为允许 NULL 值(无值或空值)。
- 在表定义中给出默认值。这表示如果不给出值,将使用默认值。
降低插入优先级 INSERT LOW PRIORITY INTO
↑ LOW PRIORITY
同样适用于 UPDATE
和 DELETE
语句
提高 INSERT 的性能 一次插入多条记录可以提高数据库处理的性能,因为MySQL用单条 INSERT 语句处理多个插入比使用多条 INSERT语句快。
INSERT SELECT 中的列名 MySQL不关心 SELECT 返回的列名。它使用的是列的位置,因此 SELECT 中的第一列(不管其列名)将用来填充表列中指定的第一个列,第二列将用来填充表列中指定的第二个列,如此等等。这对于从使用不同列名的表中导入数据是非常有用的。
更新和删除数据
好习惯:
- 除非确实打算更新和删除每一行,否则绝对不要使用不带 WHERE子句的 UPDATE 或 DELETE 语句。
- 在对 UPDATE 或 DELETE 语句使用 WHERE 子句前,应该先用 SELECT 进行测试,保证它过滤的是正确的记录,以防编写的 WHERE 子句不正确。
- 使用强制实施引用完整性的数据库,这样MySQL将不允许删除具有与其他表相关联的数据的行。
更新数据 UPDATE
UPDATE `表名`
SET `列1`="值1", `列2`="值2"
WHERE ...;
-- IGNORE, 更新多行时, 忽略错误
UPDATE IGNORE `表名`
SET ...
WHERE ...;
IGNORE 关键字 如果用 UPDATE 语句更新多行,并且在更新这些行中的一行或多行时出一个现错误,则整个 UPDATE 操作被取消(错误发生前更新的所有行被恢复到它们原来的值)。为即使是发生错误,也继续进行更新,可使用 IGNORE 关键字
删除数据 DELETE
DELETE FROM `表名`
WHERE ...;
删除表的内容而不是表 DELETE 语句从表中删除行,甚至是删除表中所有行。但是, DELETE 不删除表本身。
更快的删除 如果想从表中删除所有行,不要使用 DELETE 。可使用 TRUNCATE TABLE 语句,它完成相同的工作,但速度更快( TRUNCATE 实际是删除原来的表并重新创建一个表,而不是逐行删除表中的数据)。
创建和操纵表
创建表 CREATE TABLE
-- 示例
CREATE TABLE IF NOT EXISTS `user_accounts`(
`id` int(100) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
`password` varchar(64) NOT NULL COMMENT '用户密码',
`reset_password` tinyint(2) NOT NULL DEFAULT 0 COMMENT '用户类型:0-不需要重置密码;1-需要重置密码',
`mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '手机',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- 创建唯一索引, 不允许重复
UNIQUE KEY idx_user_mobile(`mobile`) -- 索引名可忽略: UNIQUE INDEX (`mobile`)
-- 创建外键
-- FOREIGN KEY (`dept_id`) REFERENCES `depts`(`id`) ON DELETE cascade
-- PRIMARY KEY (`id`)
-- PRIMARY KEY (`key1`,`key2`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
主键值 必须唯一。表中的每个行必须具有唯一的主键值。如果主键使用单个列,则它的值必须唯一。如果使用多个列,则这些列的组合值必须唯一。
eg. 多个列的组合作为主键
CREATE TABLE IF NOT EXISTS orderitems ( order_num int NOT NULL, order_item int NOT NULL, prod_id char(10) NOT NULL, quantity int NOT NULL, item_price decimal(8,2) NOT NULL, PRIMARY KEY(order_num, order_item) ) ENGINE=InnoDB;
orderitems 表包含orders表中每个订单的细节。每个订单有多项物品,但每个订单任何时候都只有1个第一项物品,1个第二项物品,如此等等。因此,订单号( order_num 列)和订单物品( order_item 列)的组
合是唯一的,从而适合作为主键
NULL值就是没有值或缺值。允许 NULL 值的列也允许在插入行时不给出该列的值。不允许 NULL 值的列不接受该列没有值的行,
理解 NULL 不要把 NULL 值与空串相混淆。 NULL 值是没有值,它不是空串。如果指定 '' (两个单引号,其间没有字符),这在 NOT NULL 列中是允许的。空串是一个有效的值,它不是无值。 NULL 值用关键字 NULL 而不是空串指定。
主键和 NULL 值 主键为其值唯一标识表中每个行的列。主键中只能使用不允许 NULL 值的列。允许 NULL 值的
列不能作为唯一标识。
AUTO_INCREMENT
- 每个表只允许一个 AUTO_INCREMENT 列,而且它必须被索引(如,通过使它成为主键)
- 在 INSERT 语句中指定一个值,只要它是唯一的(至今尚未使用过)即可,该值将被用来替代自动生成的值。后续的增量将开始使用该手工插入的值。
-
last_insert_id()
函数返回最后一个 AUTO_INCREMENT 值.eg. 增加一个新订单
- orders 表中创建一行
- 使用
last_insert_id()
获取自动生成的order_num
值 - 在 orderitms 表中对订购的每项物品创建一行。 order_num 在 orderitems 表中与订单细节一起存储。
DEFAULT
-
使用当前时间作为默认值
CREATE TABLE `表名`( ..., `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP );
- 许多数据库开发人员使用默认值而不是 NULL 列,特别是对用于计算或数据分组的列更是如此。
ENGINE
- InnoDB 是一个可靠的事务处理引擎,它不支持全文本搜索;
- MEMORY 在功能等同于 MyISAM ,但由于数据存储在内存(不是磁盘)中,速度很快(特别适合于临时表);
- MyISAM 是一个性能极高的引擎,它支持全文本搜索(参见第18章),但不支持事务处理。
外键不能跨引擎 混用引擎类型有一个大缺陷。外键(用于强制实施引用完整性,如第1章所述)不能跨引擎,即使用一个引擎的表不能引用具有使用不同引擎的表的外键。
更新表 ALTER TABLE
列和外键的操作
-- 新增列
ALTER TABLE `表名` ADD COLUMN `列名` 列属性;
-- 删除列
ALTER TABLE `表名` DROP COLUMN `列名`;
-- 修改列(属性替换)
-- CHANGE 可以重命名列名, MODIFY 不能
ALTER TABLE `表名` CHANGE COLUMN `旧列名` `新列名` 列属性;
ALTER TABLE `表名` MODIFY `列名` 列属性;
-- 删除表
DROP TABLE `表名`;
-- 重命名表
RENAME TABLE `表名1` TO `表名2`;
复杂的表结构更改一般需要手动删除过程
用新的列布局创建一个新表;
- 使用 INSERT SELECT 语句从旧表复制数据到新表。如果有必要,可使用转换函数和计算字段;
- 检验包含所需数据的新表;
- 重命名旧表(如果确定,可以删除它);
- 用旧表原来的名字重命名新表;
- 根据需要,重新创建触发器、存储过程、索引和外键。
小心使用 ALTER TABLE 使用 ALTER TABLE 要极为小心,应该在进行改动前做一个完整的备份(模式和数据的备份)。数据库表的更改不能撤销,如果增加了不需要的列,可能不能删除它们。类似地,如果删除了不应该删除的列,可能会丢失该列中的所有数据。
约束, 索引
-- 删除外键
-- 约束名可以用 show create table `表名` 语句来查看
ALTER TABLE `表名` DROP FOREIGN KEY `约束名`;
-- 查看索引
SHOW INDEX FROM `表名`;
SHOW KEY FROM `表名`;
-- 创建普通索引(省略索引名)
ALTER TABLE `表名` ADD INDEX (`列名`);
ALTER TABLE `表名` ADD UNIQUE KEY(`列名`);
ALTER TABLE `表名` ADD PRIMARY KEY(`列名`);
ALTER TABLE `表名` ADD FOREIGN KEY(`列名`) REFERENCES `关联表名`(`关联列名`);
ALTER TABLE `表1` ADD CONSTRAINT `约束名` FOREIGN KEY (`外键`) REFERENCES `表2` (`表2的键`);
-- CREATE INDEX 只可对表增加普通索引或UNIQUE索引
CREATE INDEX `索引名` ON `表名` (`列名`);
CREATE UNIQUE INDEX `索引名` ON `表名` (`列名`);
-- 删除索引
ALTER TABLE `表名` DROP PRIMARY KEY;
ALTER TABLE `表名` DROP INDEX `索引名`;
ALTER TABLE `表名` DROP FOREIGN KEY `约束名`;
DROP INDEX `索引名` ON `表名`;
索引
2019年5月29日17:18:22 开始补充
种类:
-
INDEX
普通索引:仅加速查询 -
UNIQUE KEY
唯一索引:加速查询 + 列值唯一(可以有null) -
PRIMARY KEY
主键索引:加速查询 + 列值唯一 + 表中只有一个(不可以有null) -
INDEX
组合索引:多列值组成一个索引,专门用于组合搜索,其效率大于索引合并 -
FULLTEXT
全文索引:对文本的内容进行分词,进行搜索
术语
- 索引合并:使用多个单列索引组合查询搜索
- 覆盖索引:select的数据列只用从索引中就能够取得,不必读取数据行,换句话说查询列要被所建的索引覆盖
组合索引
- 同时搜索多个条件时,组合索引的性能效率好过于多个单一索引合并
SQL Explain
备注: 此处将仅作为存档, 后续更新将在 这里 中处理. 2019年10月18日15:53:53
EXPLAIN 结果字段分析
-
select_type
查询类型 说明 SIMPLE 简单查询
不包含UNION查询或子查询PRIMARY 最外层查询 SUBQUERY 子查询中的第一个 SELECT DEPENDENT SUBQUERY !!! 子查询, 但依赖于外层查询的结果
注意确认, 避免大表驱动小表DERIVED 子查询 UNION 联合 UNION RESULT 使用联合的结果 - table
访问的表名
- partitions
匹配的分区
-
type
查询时的访问方式, 性能:all < index < range < index_merge < ref_or_null < ref < eq_ref < system/const
访问方式 说明 ALL 全表扫描,对于数据表从头到尾找一遍 select * from tb1;
特别的:如果有limit限制,则找到之后就不在继续向下扫描select * from tb1 where email = '[email protected]'
select * from tb1 where email = '[email protected]' limit 1;
虽然上述两个语句都会进行全表扫描,第二句使用了limit,则找到一个后就不再继续扫描。INDEX 全索引扫描,对索引从头到尾找一遍 select nid from tb1;
RANGE 对索引列进行范围查找 INDEX_MERGE 合并索引,使用多个单列索引搜索 REF 使用索引快速定位(根据索引查找一个或多个值) EQ_REF 通常出现在多表的join查询, 连接时使用primary key 或 unique 索引(都只能匹配到一行记录) CONST 通过主键或唯一索引精确查找到一行
常量
表最多有一个匹配行(主键或唯一索引),因为仅有一行,在这行的列值可被优化器剩余部分认为是常数,const表很快,因为它们只读取一次SYSTEM 系统
表仅有一行(=系统表)。这是const联接类型的一个特例。 - possible_keys
表示查询时,可能使用的索引
- key
实际使用的索引
-
key_len
使用索引字段长度, 该字段可以评估组合索引是否完全被使用或仅仅是最左前缀被用到.
计算规则
-
字符串
- char(n): n 字节长度
- varchar(n): 如果是 utf8 编码, 则是 3 n + 2字节; 如果是 utf8mb4 编码, 则是 4 n + 2 字节.
-
数值类型:
- TINYINT: 1字节
- SMALLINT: 2字节
- MEDIUMINT: 3字节
- INT: 4字节
- BIGINT: 8字节
-
时间类型
- DATE: 3字节
- TIMESTAMP: 4字节
- DATETIME: 8字节
- 字段属性: NULL 属性 占用一个字节. 如果一个字段是 NOT NULL 的, 则没有此属性.
-
- ref
列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
- row
估算的需要扫描的行数
- filtered
-
Extra
该列包含MySQL解决查询的详细信息
值 说明 Using filesort mysql对结果排序进行额外排序.
mysql有两种文件排序算法,这两种排序方式都可以在内存或者磁盘上完成
explain不会告诉你mysql将使用哪一种文件排序
也不会告诉你排序会在内存里还是磁盘上完成。Using index 使用覆盖索引,以避免访问表。不要把覆盖索引和index访问类型弄混了。 Using index condition 索引下推优化, 5.6新增特性 Using temporary 意味着mysql在对查询结果排序时会使用一个临时表 Using where 这意味着mysql服务器将在存储引擎检索行后再进行过滤
许多where条件里涉及索引中的列,当(并且如果)它读取索引时,就能被存储引擎检验
因此不是所有带where子句的查询都会显示“Using where”。
有时“Using where”的出现就是一个暗示:查询可受益于不同的索引。Range checked for each record(index map: N) 这个意味着没有好用的索引,新的索引将在联接的每一行上重新估算,N是显示在possible_keys列中索引的位图,并且是冗余的。
Profile
备注: 此处将仅作为存档, 后续更新将在 这里 中处理. 2019年10月18日15:53:53
show profile 分析SQL性能工具(检测数据存在临时表中)
- 开启profile
SET profiling=1;
- 发送sql
- 查看profile的资源开销结果
show profiles
,show profile
,show profile for query
- 关闭profile
视图
视图是虚拟的表。与包含数据的表不一样,视图只包含使用时动态检索数据的查询。
视图包含的是一个SQL查询, 它不包含数据!!个人理解: 视图即别名~
-- 创建视图
CREATE VIEW `视图名` AS SELECT ...
-- 查看创建指定视图的语句
SHOW CREATE VIEW `视图名`;
-- 删除视图
DROP VIEW `视图名`;
-- 更新视图
CREATE OR REPLACE VIEW AS SELECT ...
视图的作用:
- 简化数据处理
- 重新格式化基础数据
- 保护基础数据
为什么使用视图:
- 重用SQL语句。
- 简化复杂的SQL操作。在编写查询后,可以方便地重用它而不必知道它的基本查询细节。
- 使用表的组成部分而不是整个表。
- 保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限。
- 更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
视图的规则和限制
- 与表一样,视图必须唯一命名(不能给视图取与别的视图或表相同的名字)。
- 对于可以创建的视图数目没有限制。
- 为了创建视图,必须具有足够的访问权限。这些限制通常由数据库管理人员授予。
- 视图可以嵌套,即可以利用从其他视图中检索数据的查询来构造一个视图。
- ORDER BY 可以用在视图中,但如果从该视图检索数据 SELECT 中也含有 ORDER BY ,那么该视图中的 ORDER BY 将被覆盖。
- 视图不能索引,也不能有关联的触发器或默认值。
- 视图可以和表一起使用。例如,编写一条联结表和视图的 SELECT语句。
创建可重用的视图
创建不受特定数据限制的视图是一种好办法。例如,上面创建的视图返回生产所有产品的客户而不仅仅是 生产TNT2 的客户。扩展视图的范围不仅使得它能被重用,而且甚至更有用。这样做不需要创建和维护多个类似视图。
将视图用于检索
一般,应该将视图用于检索( SELECT 语句)而不用于更新( INSERT 、 UPDATE 和 DELETE )。
存储过程
存储过程
简单来说: 存储过程是为以后的使用而保存的一条或多条MySQL语句的集合.
使用存储过程的原因:
- 封装复杂操作, 统一调用
-
提高性能
使用存储过程比使用单独的SQL语句要快
- 存在一些只能用在单个请求中的MySQL元素和特性,存储过程可以使用它们来编写功能更强更灵活的代码
存储过程一般并不显示结果, 而是把结果返回给你指定的变量.
-- 调用存储过程
CALL `过程名`()
-- 更改mysql命令行客户端语句分隔符, 除了 \ 符号外,其他字符都可以作为语句分隔符.
DELIMITER //
-- 创建存储过程
CREATE PROCEDURE `过程名`()
BEGIN
END//
-- 还原mysql命令行客户端语句分隔符
DELIMITER ;
-- 删除存储过程
DROP PROCEDURE IF EXISTS `过程名`;
-- 检查(查看)存储过程
SHOW CREATE PROCEDURE `过程名`;
-- 查看存储过程的元数据(创建时间, 创建人..)
SHOW PROCEDURE STATUS LIKE '过程名';
MySQL支持存储过程的参数:
- IN
- OUT
- INOUT
参数的数据类型
存储过程的参数允许的数据类型与表中使用的数据类型相同。!!! 记录集不是允许的类型,因此,不能通过一个参数返回多个行和列。
一个简单的示例: 计算商品的最低、最高、平均价格
DELIMITER //
CREATE PROCEDURE productpricing(
OUT pl DECIMAL(8,2),
OUT ph DECIMAL(8,2),
OUT pm DECIMAL(8,2)
)
BEGIN
SELECT Min(prod_price) INTO pl FROM products;
SELECT Max(prod_price) INTO ph FROM products;
SELECT Avg(prod_price) INTO pm FROM products;
END//
DELIMITER ;
CALL productpricing(@pricelow, @pricehigh, @priceaverage);
SELECT @pricelow, @pricehigh, @priceaverage;
另一个简单示例: 接受订单号并返回该订单的统计
DELIMITER //
CREATE PROCEDURE ordertotal(IN onumber INT, OUT ototal DECIMAL(6,2))
BEGIN
SELECT Sum(item_price*quantity)
FROM orderitems
WHERE order_num = onumber
INTO ototal;
END//
DELIMITER ;
CALL ordertotal(20005, @total);
SELECT @total;
变量
变量(variable)
内存中一个特定的位置,用来临时存储数据。变量名不区分大小写
用户变量
@变量名
, 仅对当前连接有效
可以使用 SET @变量=值
或 SELECT @变量:=值;
来赋值给变量
--
SET @变量=值;
-- 在SELECT中, = 是比较符, 因此需要用 :=
SELECT @变量:=值;
系统变量
全局变量
对当前mysqld实例有效
SET GLOBAL 变量名=值;
SET @@变量名=值;
需要 SUPER 权限, 需重新连接后才生效.
会话变量
只对当前连接有效
-- 设置变量值
SET SESSION 变量名=值;
-- LOCAL 是SESSION的同义词
SET LOCAL 变量名=值;
-- 不指定 GLOBAL,SESSION 时, 默认就是 SESSION
-- 此处没有 @
SET 变量名=值;
-- 若存在会话变量则返回, 否则返回全局变量.
SELECT @@变量名;
SHOW VARIABLES LIKE '变量名';
局部变量
declare
定义, 仅在当前块有效(begin...end)
语法
条件语句
IF ... THEN
ELSEIF ... THEN
ELSE
END IF;
循环语句
-- WHILE 循环
WHILE ... DO
END WHILE;
-- ---------------------------------------------------
-- REPEAT 循环
REPEAT
UNTIL ...
END REPEAT;
-- ---------------------------------------------------
-- LOOP 循环
loop标记: LOOP
IF ... THEN
LEAVE loop标记;
END IF;
END LOOP;
-- LOOP 示例
CREATE PROCEDURE proc_loop ()
BEGIN
declare i int default 0;
loop_label: loop
select i;
set i=i+1;
if i>=5 then
leave loop_label;
end if;
end loop;
END
动态执行SQL语句
PREPARE 变量 FROM "...";
EXECUTE 变量 USING @p1;
DEALLOCATE PREPARE 变量;
-- 示例
SET @num = 20005;
PREPARE stmt FROM 'SELECT * FROM orders WHERE order_num = ?';
EXECUTE stmt USING @num;
DEALLOCATE PREPARE stmt;
参数只能使用 用户变量来传递
智能存储过程
在存储过程中包含:
- 业务规则
- 智能处理
-- Name: ordertotal
-- Parameters: onumber = order number
-- taxable = 0 if not taxable, 1 if taxable
-- ototal = order total VARIABLES
CREATE PROCEDURE ordertotal(
IN onumber INT,
IN taxable BOOLEAN,
OUT ototal DECIMAL(8,2)
) COMMENT '获取订单总额, 可选增加营业税'
BEGIN
-- Declare variable for total
DECLARE total DECIMAL(8,2);
-- Declare tax percentage
DECLARE taxrate INT DEFAULT 6;
-- Get the order total
SELECT sum(item_price*quantity) FROM orderitems WHERE order_num = onumber INTO total;
-- Is this taxable?
IF taxable THEN
-- yes, so add taxrate to the total
SELECT total+(total/100*taxrate) INTO total;
END IF;
-- And finally, save to out vaiable
SELECT total INTO ototal;
END;
- DECLARE 定义了两个局部变量, 支持可选的默认值
游标
游标(cursor)是一个存储在MySQL服务器上的数据库查询,它不是一条 SELECT 语句,而是被该语句检索出来的结果集。在存储了游标之后,应用程序可以根据需要滚动或浏览其中的数据。
只能用于存储过程 不像多数DBMS,MySQL游标只能用于存储过程(和函数)。
使用游标的步骤:
- 声明游标(仅定义查询语句)
- 打开游标(执行查询)
- 使用游标检索数据(遍历)
-
关闭游标
- 游标关闭后必须重新打开才能再次使用
- 存储过程结束时会自动将已打开的游标关闭
CREATE PROCEDURE `processorders`()
BEGIN
-- create cursor
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
-- open cursor
OPEN ordernumbers;
-- close cursor
CLOSE ordernumbers;
END
书上示例
DROP PROCEDURE processorders;
CREATE PROCEDURE processorders()
BEGIN
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;
DECLARE t DECIMAL(8,2);
-- Declare CURSOR
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
-- Declare continue handler
-- FOR NOT FOUND
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
-- Create a table to store the results
CREATE TABLE IF NOT EXISTS ordertotals(
order_num INT,
total DECIMAL(8,2)
);
OPEN ordernumbers;
REPEAT
-- Get order number
FETCH ordernumbers INTO o;
CALL ordertotal(o,1,t);
INSERT INTO ordertotals(order_num,total) VALUES(o,t);
UNTIL done
END REPEAT;
-- Close the cursor
CLOSE ordernumbers;
END
改进示例
DROP PROCEDURE processorders;
CREATE PROCEDURE processorders()
BEGIN
DECLARE done BOOLEAN DEFAULT 0;
DECLARE o INT;
DECLARE t DECIMAL(8,2);
-- Declare CURSOR
DECLARE ordernumbers CURSOR
FOR
SELECT order_num FROM orders;
-- Declare continue handler
-- FOR NOT FOUND
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;
-- Create a table to store the results
CREATE TABLE IF NOT EXISTS ordertotals(
order_num INT,
total DECIMAL(8,2)
);
OPEN ordernumbers;
FETCH ordernumbers INTO o;
-- 避免插入 (NULL,NULL) 到 ordertotals 表
WHILE NOT done DO
CALL ordertotal(o,1,t);
SELECT o,t;
INSERT INTO ordertotals(order_num,total) VALUES(o,t);
FETCH ordernumbers INTO o;
END WHILE;
-- Close the cursor
CLOSE ordernumbers;
END
触发器
触发器是MySQL响应以下任意语句而自动执行的一条MySQL语句(或位于 BEGIN 和 END 语句之间的一组语
句):
- DELETE ;
- INSERT ;
- UPDATE
创建触发器可能需要特殊的安全访问权限,但是,触发器的执行是自动的。如果 INSERT 、 UPDATE 或 DELETE 语句能够执行,则相关的触发器也能执行。
- 应该用触发器来保证数据的一致性(大小写、格式等)。在触发器中执行这种类型的处理的优点是它总是进行这种处理,而且是透明地进行,与客户机应用无关。
- 触发器的一种非常有意义的使用是创建审计跟踪。使用触发器,把更改(如果需要,甚至还有之前和之后的状态)记录到另一个表非常容易。
- 遗憾的是,MySQL触发器中不支持 CALL 语句。这表示不能从触发器内调用存储过程。所需的存储过程代码需要复制到触发器内。
创建触发器
在创建触发器时,需要给出4条信息:
- 唯一的触发器名;
- 触发器关联的表;
- 触发器应该响应的活动( DELETE 、 INSERT 或 UPDATE );
- 触发器何时执行(处理之前或之后)。
触发器按每个表每个事件每次地定义,每个表每个事件每次只允许一个触发器。因此,每个表最多支持6个触发器(每条 INSERT 、 UPDATE和 DELETE 的之前和之后)。单一触发器不能与多个事件或多个表关联,所以,如果你需要一个对 INSERT 和 UPDATE 操作执行的触发器,则应该定义两个触发器。
仅支持表 只有表才支持触发器,视图不支持(临时表也不支持)
-- 创建触发器
CREATE TRIGGER `触发器名`
AFTER|BEFORE
INSERT|UPDATE|DELETE
ON `表名`
FOR EACH ROW
...
-- 删除触发器
DROP TRIGGER `触发器名`;
BEFORE 或 AFTER ? 通常,将 BEFORE 用于数据验证和净化(目的是保证插入表中的数据确实是需要的数据)。本提示也适用于 UPDATE 触发器。
INSERT 触发器
- 在 INSERT 触发器代码内,可引用一个名为 NEW 的虚拟表,访问被插入的行;
- 在 BEFORE INSERT 触发器中, NEW 中的值也可以被更新(允许更改被插入的值);
- 对于 AUTO_INCREMENT 列, NEW 在 INSERT 执行之前包含 0 ,在 INSERT执行之后包含新的自动生成值。
-- mysql中无法执行: 禁止触发器返回结果集
-- ERROR 1415 (0A000): Not allowed to return a result set from a trigger
CREATE TRIGGER neworder AFTER INSERT ON orders
FOR EACH ROW
SELECT NEW.order_num;
DELETE 触发器
- 在 DELETE 触发器代码内,你可以引用一个名为 OLD 的虚拟表,访问被删除的行;
- OLD 中的值全都是只读的,不能更新。
在任意订单被删除前将执行此触发器。它使用一条 INSERT 语句将 OLD 中的值(要被删除的订单)保存到一个名为 archive_orders 的存档表中
CREATE TRIGGER deleteorders BEFORE DELETE ON orders
FOR EACH ROW
BEGIN
INSERT INTO archive_orders(order_num,order_date,cust_id)
VALUES(OLD.order_num,OLD.order_date,OLD.cust_id);
END
使用 BEFORE DELETE 触发器的优点(相对于 AFTER DELETE 触发器来说)为,如果由于某种原因,订单不能存档, DELETE 本身将被放弃。
UPDATE 触发器
- 在 UPDATE 触发器代码中,你可以引用一个名为 OLD 的虚拟表访问以前( UPDATE 语句前)的值,引用一个名为 NEW 的虚拟表访问新更新的值;
- 在 BEFORE UPDATE 触发器中, NEW 中的值可能也被更新(允许更改将要用于 UPDATE 语句中的值);
- OLD 中的值全都是只读的,不能更新。
CREATE TRIGGER updatevendor
BEFORE UPDATE ON vendors
FOR EACH ROW
SET NEW.vend_state=Upper(NEW.vend_state);
事务
事务存在的问题:
- 脏读
- 不可重复读
- 幻读
参考: https://www.cnblogs.com/balfi...
数据库事务隔离级别:
- read-uncommited 读未提交
存在所有问题, 最低的隔离级别。一个事务可以读取另一个事务并未提交的更新结果。
- read-commited 读已提交
解决"脏读", 大部分数据库采用的默认隔离级别。一个事务的更新操作结果只有在该事务提交之后,另一个事务才可以的读取到同一笔数据更新后的结果。
- repeatable-read 可重复读
解决"不可重复读", 整个事务过程中,对同一笔数据的读取结果是相同的,不管其他事务是否在对共享数据进行更新,也不管更新提交与否。
- serializable 序列化
解决"幻读", 最高隔离级别。所有事务操作依次顺序执行。注意这会导致并发度下降,性能最差。通常会用其他并发级别加上相应的并发锁机制来取代它。
- 不可重复读
- 可重复读
- 幻读
MySQL 默认级别是 repeatable-read
术语
- 事务( transaction )指一组SQL语句;
- 回退( rollback )指撤销指定SQL语句的过程;
- 提交( commit )指将未存储的SQL语句结果写入数据库表;
- 保留点( savepoint )指事务处理中设置的临时占位符(place-holder),你可以对它发布回退(与回退整个事务处理不同)。
-- 标识事务开始
START TRANSACTION;
-- ROLLBACK;
-- COMMIT;
哪些语句可以回退? 事务处理用来管理 INSERT 、 UPDATE 和DELETE 语句。你不能回退 SELECT 语句。(这样做也没有什么意义。)你不能回退 CREATE 或 DROP 操作。事务处理块中可以使用这两条语句,但如果你执行回退,它们不会被撤销。
-- 关闭本次连接的mysql自动提交
SET autocommit=0;
保留点 SavePoint
为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符。这样,如果需要回退,可以回退到某个占位符。
-- 创建保留点
SAVEPOINT `保留点名`;
-- 回退到指定保留点
ROLLBACK TO `保留点名`;
字符集
术语
- 字符集为字母和符号的集合;
- 编码为某个字符集成员的内部表示;
- 校对为规定字符如何比较的指令。
-- 查看可用的字符集
SHOW CHARACTER SET;
SHOW CHARSET;
-- 查看可用的校对
SHOW COLLATION;
-- 查看当前使用的字符集
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';
通常系统管理在安装时定义一个默认的字符集和校对。此外,也可以在创建数据库时,指定默认的字符集和校对。
实际上,字符集很少是服务器范围(甚至数据库范围)的设置。不同的表,甚至不同的列都可能需要不同的字符集,而且两者都可以在创建表时指定。
CREATE TABLE mytable(
column1 INT,
column2 VARCHAR(10),
-- 指定特定列使用特定的字符集及校对
column3 VARCHAR(10) CHARSET latin1 COLLATE latin1_general_ci
) Engine=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
-- 临时区分大小写排序
SELECT * FROM mytable
ORDER BY column3 COLLATE latin1_general_cs;
SELECT 的其他 COLLATE 子句 除了这里看到的在 ORDER BY子句 中使用以外, COLLATE 还可以用于 GROUP BY 、 HAVING 、聚集函数、别名等。
串可以在字符集之间进行转换。为此,使用 Cast() 或 Convert ()函数
安全管理
用户应该对他们需要的数据具有适当的访问权,既不能多也不能少
不要使用 root 应该严肃对待 root 登录的使用。仅在绝对需要时使用它(或许在你不能登录其他管理账号时使用)。不应该在日常的MySQL操作中使用 root 。
- MySQL用户账号和信息存储在名为 mysql 的MySQL数据库中。
- mysql 数据库有一个名为 user 的表,它包含所有用户账号。
-- 查看当前所有用户
SELECT host,user FROM mysql.user;
-- 创建用户
CREATE USER 用户名 IDENTIFIED BY '密码';
-- 重命名用户账号
RENAME USER 旧用户名 TO 新用户名;
-- 删除用户账号
DROP USER 用户名;
-- 修改用户名
RENAME USER '旧用户名@..' TO '新用户名@..';
-- 更改自己口令
SET PASSWORD = Password('密码');
-- 更改指定用户口令
SET PASSWORD FOR 用户名 = Password('密码');
ALTER USER 用户名 IDENTIFIED BY '密码';
mysqladmin
-- 设置密码(若修改密码, 则需输入原密码)
mysqladmin -u root password
设置访问权限
-- 查看赋予当前用户账号的权限
SHOW GRANTS;
-- 查看赋予某个用户账号的权限
-- 完整的用户定义: user@host
-- 不指定主机名时使用默认的主机名 %
-- 默认查询: 用户名@'%'
SHOW GRANTS FOR 用户名;
-- 创建账号并赋予最高权限
GRANT ALL ON *.* TO '用户名'@'%' IDENTIFIED BY '密码' WITH GRANT OPTION;
-- 赋予 SELECT 权限
GRANT SELECT ON `数据库名`.* TO 用户名;
-- 撤销 SELECT 权限
REVOKE SELECT ON `数据库名`.* FROM 用户名;
新用户的权限为: GRANT USAGE ON *.* TO 'yjx'@'%'
, 即没有任何权限.
常用权限
权限 | 说明 |
---|---|
ALL | 除GRANT OPTION外的所有权限 |
SELECT | 仅查询 |
SELECT,INSERT | 查询和插入 |
USAGE | 无权限 |
目标
目标 | 说明 |
---|---|
数据库名.* | 指定数据库所有 |
数据库名.表 | 指定数据库的指定表 |
数据库名.存储过程 | 指定数据库的指定存储过程 |
. | 所有数据库 |
用户
用户 | 说明 |
---|---|
用户名@ip | 指定ip登陆的用户 |
用户名@'192.168.1.%' | 指定ip段登陆的用户 |
用户名@'%' | 任意ip下登陆的用户 |
用户名@localhost | 本地登陆的用户 |
用户名@‘192.168.200.0/255.255.255.0’ |
(子网掩码配置) |
GRANT 和 REVOKE 可在几个层次上控制访问权限:
- 整个服务器,使用 GRANT ALL 和 REVOKE ALL;
- 整个数据库,使用 ON database.*;
- 特定的表,使用 ON database.table;
- 特定的列;
- 特定的存储过程。
数据库维护
数据库备份
- msyqldump 程序
-
BACKUP TABLE
或SELECT INTO OUTFILE
语句转储数据到外部文件, 使用RESTORE TABLE
还原
mysqldump 备份
# 导出为文本文件
mysqldump -uroot -p -B '数据库名1' '数据库名2' > /tmp/mysql.bak
# 直接导出为压缩文件
mysqldump -uroot -p -B '数据库名1' '数据库名2' | gzip > /tmp/mysql.bak.gz
# -A, --all-databases 备份所有库
# -B, --database 备份指定库
# -F 刷新binlog日志
# -x,--lock-all-tables
# -l,--locktables
# --single-transaction 适合innodb事务数据库备份
# --default-character-set=utf8 字符集
# --triggers 备份触发器
# -d, --no-data 只备份表结构
# -t, --no-create-info 只备份数据, 无 create table 语句
# --master-data 增加binlog日志文件名及对应的位置点
# 生产环境全备份
# 进行数据库全备,(生产环境还通过定时任务每日凌晨执行)
mysqldump -uroot -p123456 -S /data/3306/mysql.sock --single-transaction -F -B "数据库名" | gzip > /server/backup/mysql_$(date +%F).sql.gz
# innodb引擎备份
mysqldump -u$MYUSER -p$MYPASS -S $MYSOCK -F --single-transaction -A | gzip > $DATA_FILE
# myisam引擎备份
mysqldump -u$MYUSER -p$MYPASS -S $MYSOCK -F -A -B --lock-all-tables |gzip >$DATA_FILE
恢复
# 直接从sql文件恢复
mysql -uroot -p < /tmp/mysql.sql
# 从压缩文件中恢复
gunzip < /tmp/mysql.bak.gz | mysql -uroot -p
改善性能
-
SHOW PROCESSLIST
显示所有活动进程(以及它们的线程ID和执行时间)。你还可以用KILL
命令终结某个特定的进程(使用这个命令需要作为管理员登录)。 - 使用 EXPLAIN 语句让MySQL解释它将如何执行一条 SELECT 语句。
- 在导入数据时,应该关闭自动提交。你可能还想删除索引(包括FULLTEXT 索引),然后在导入完成后再重建它们。
- 你的 SELECT 语句中有一系列复杂的 OR 条件吗?通过使用多条SELECT 语句和连接它们的 UNION 语句,你能看到极大的性能改进。
- 索引改善数据检索的性能,但损害数据插入、删除和更新的性能。如果你有一些表,它们收集数据且不经常被搜索,则在有必要之前不要索引它们。(索引可根据需要添加和删除。)
- LIKE 很慢。一般来说,最好是使用 FULLTEXT 而不是 LIKE 。
加快数据库恢复速度
在恢复数据时,可能会导入大量的数据。
此时有一些技巧可以提高导入速度:
-
导入时禁用索引, 导入结束后再开启索引
ALTER TABLE 表名 disable keys; ALTER TABLE 表名 enable keys;
-
对于InnoDB, 由于默认
autocommit=1
, 即每条语句作为单独的事务, 因此可以将多条合并成少数几条事务以加快速度.-- 关闭自动提交, 也可以用 begin 或 start transaction set autocommit = 0; -- 插入若干条提交 insert into ...; ... insert into ...; commit; -- 插入若干条提交 insert into ...; ... insert into ...; commit; set autocommit = 1;
查看表锁情况
mysql> show open tables where in_use > 0;
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| test_yjx | user | 1 | 0 |
+----------+-------+--------+-------------+
慢查询分析
备注: 此处将仅作为存档, 后续更新将在 这里 中处理. 2019年10月18日15:53:53
开启慢查询
[mysqld]
# ...此处省略了其他与慢查询日志不相关的配置
############### 慢查询日志 ################
slow_query_log=1 # 打开慢查询日志
log_output=file # 日志记录位置
slow_query_log_file=/var/run/mysqld/mysqld-slow.log # 慢查询日志记录文件
long_query_time=3 # 慢查询时间阀值
也可以在mysql shell中修改相关 global 变量来开启(重启后失效)
当前会话中临时启用慢日志分析
set global slow_query_log = 1;
set long_query_time = 0;
测试慢查询是否有效
mysql> select sleep(4);
此时日志或多出一条:
# Time: 190215 15:16:14 # User@Host: root[root] @ localhost [] # Query_time: 11.000205 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0 SET timestamp=1550214974; select sleep(11);
慢查询分析 mysqldumpslow
# 分析慢日志
mysqldumpslow -a -n 50 -s c /var/run/mysqld/mysqld-slow.log
# 参数说明
--verbose 版本
--debug 调试
--help 帮助
-v 版本
-d 调试模式
-s ORDER 排序方式
what to sort by (al, at, ar, c, l, r, t), 'at' is default
al: average lock time
ar: average rows sent
at: average query time
c: count
l: lock time
r: rows sent
t: query time
-r 反转顺序,默认文件倒序拍。reverse the sort order (largest last instead of first)
-t NUM 显示前N条just show the top n queries
-a 不要将SQL中数字转换成N,字符串转换成S。don't abstract all numbers to N and strings to 'S'
-n NUM abstract numbers with at least n digits within names
-g PATTERN 正则匹配;grep: only consider stmts that include this string
-h HOSTNAME mysql机器名或者IP;hostname of db server for *-slow.log filename (can be wildcard),
default is '*', i.e. match all
-i NAME name of server instance (if using mysql.server startup script)
-l 总时间中不减去锁定时间;don't subtract lock time from total time
设计范式
表的键和属性概念
超键 唯一标识元组(数据行)的属性集叫做超键.
比如普通表中主键 id 是超键, (id, name) 也是超键, (id, age) 也是超键, 因为都可以唯一标识元组(数据行).
候选键 最小超键, 不包含无用字段, 也称为 码.
以 超键 中的例子来讲, (id, name) 因为包含无用的 name 字段, 所以显然它不是候选键. 而单独的 id 是候选键
主键 从候选键中选择一个, 也称为 主码.
外键 数据表中的字段是别的数据表的主键, 则称它为外键.
主属性 包含在任意候选键中的属性称为主属性.
范式
所有范式(按照严格顺序排列):
- 1NF(第一范式)
关键: 表中任何属性都是原子性的, 不可再分.
解释: 字段不要是可以由其他字段组合/计算的.
- 2NF(第二范式)
需要保证表中的非主属性与候选键(码)完全依赖 (即消除了部分依赖)
- 3NF(第三范式)
需要保证表中的非主属性与候选键(码)不存在传递依赖
通常只要求到这个级别.
- BCNF(巴斯-科德范式)
消除主属性之间的部分依赖和传递依赖
- 4NF(第四范式)
- 5NF(完美范式)
这里的
- 部分依赖 也称为 部分函数依赖
- 传递依赖 也称为 传递函数依赖
通常要求: 3NF
根据实际情况, 必须时可以新增冗余字段来提高查询效率, 需要权衡.
范式的严格程度是依次递增, 且高级别范式肯定是满足低级别范式的.
存储引擎
该部分主要来自: https://juejin.im/post/5c2c53...
功能差异
show engines
Engine | Support | Comment |
---|---|---|
InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys |
MyISAM | YES | MyISAM storage engine |
存储差异
MyISAM | Innodb | |
---|---|---|
文件格式 | 数据和索引是分别存储的,数据.MYD ,索引.MYI |
数据和索引是集中存储的,.ibd |
文件能否移动 | 能,一张表就对应.frm 、MYD 、MYI 3个文件 |
否,因为关联的还有data 下的其它文件 |
记录存储顺序 | 按记录插入顺序保存 | 按主键大小有序插入 |
空间碎片(删除记录并flush table 表名 之后,表文件大小不变) |
产生。定时整理:使用命令optimize table 表名 实现 |
不产生 |
事务 | 不支持 | 支持 |
外键 | 不支持 | 支持 |
锁支持(锁是避免资源争用的一个机制,MySQL锁对用户几乎是透明的) | 表级锁定 | 行级锁定、表级锁定,锁定力度小并发能力高 |
锁扩展表级锁(
table-level lock
):lock tables
,, ... read/write unlock tables
。其中, ... read
是共享锁,一旦锁定任何客户端都不可读;write
是独占/写锁,只有加锁的客户端可读可写,其他客户端既不可读也不可写。锁定的是一张表或几张表。行级锁(
row-level lock
):锁定的是一行或几行记录。共享锁:select * from
,对查询的记录增加共享锁;where <条件> LOCK IN SHARE MODE; select * from
,对查询的记录增加排他锁。这里值得注意的是:where <条件> FOR UPDATE; innodb
的行锁,其实是一个子范围锁,依据条件锁定部分范围,而不是就映射到具体的行上,因此还有一个学名:间隙锁。比如select * from stu where id < 20 LOCK IN SHARE MODE
会锁定id
在20
左右以下的范围,你可能无法插入id
为18
或22
的一条新纪录。
课程数据
create.sql
########################################
# MySQL Crash Course
# http://www.forta.com/books/0672327120/
# Example table creation scripts
########################################
########################
# Create customers table
########################
CREATE TABLE customers
(
cust_id int NOT NULL AUTO_INCREMENT,
cust_name char(50) NOT NULL ,
cust_address char(50) NULL ,
cust_city char(50) NULL ,
cust_state char(5) NULL ,
cust_zip char(10) NULL ,
cust_country char(50) NULL ,
cust_contact char(50) NULL ,
cust_email char(255) NULL ,
PRIMARY KEY (cust_id)
) ENGINE=InnoDB;
#########################
# Create orderitems table
#########################
CREATE TABLE orderitems
(
order_num int NOT NULL ,
order_item int NOT NULL ,
prod_id char(10) NOT NULL ,
quantity int NOT NULL ,
item_price decimal(8,2) NOT NULL ,
PRIMARY KEY (order_num, order_item)
) ENGINE=InnoDB;
#####################
# Create orders table
#####################
CREATE TABLE orders
(
order_num int NOT NULL AUTO_INCREMENT,
order_date datetime NOT NULL ,
cust_id int NOT NULL ,
PRIMARY KEY (order_num)
) ENGINE=InnoDB;
#######################
# Create products table
#######################
CREATE TABLE products
(
prod_id char(10) NOT NULL,
vend_id int NOT NULL ,
prod_name char(255) NOT NULL ,
prod_price decimal(8,2) NOT NULL ,
prod_desc text NULL ,
PRIMARY KEY(prod_id)
) ENGINE=InnoDB;
######################
# Create vendors table
######################
CREATE TABLE vendors
(
vend_id int NOT NULL AUTO_INCREMENT,
vend_name char(50) NOT NULL ,
vend_address char(50) NULL ,
vend_city char(50) NULL ,
vend_state char(5) NULL ,
vend_zip char(10) NULL ,
vend_country char(50) NULL ,
PRIMARY KEY (vend_id)
) ENGINE=InnoDB;
###########################
# Create productnotes table
###########################
CREATE TABLE productnotes
(
note_id int NOT NULL AUTO_INCREMENT,
prod_id char(10) NOT NULL,
note_date datetime NOT NULL,
note_text text NULL ,
PRIMARY KEY(note_id),
FULLTEXT(note_text)
) ENGINE=MyISAM;
#####################
# Define foreign keys
#####################
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_orders FOREIGN KEY (order_num) REFERENCES orders (order_num);
ALTER TABLE orderitems ADD CONSTRAINT fk_orderitems_products FOREIGN KEY (prod_id) REFERENCES products (prod_id);
ALTER TABLE orders ADD CONSTRAINT fk_orders_customers FOREIGN KEY (cust_id) REFERENCES customers (cust_id);
ALTER TABLE products ADD CONSTRAINT fk_products_vendors FOREIGN KEY (vend_id) REFERENCES vendors (vend_id);
populate.sql
########################################
# MySQL Crash Course
# http://www.forta.com/books/0672327120/
# Example table population scripts
########################################
##########################
# Populate customers table
##########################
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10001, 'Coyote Inc.', '200 Maple Lane', 'Detroit', 'MI', '44444', 'USA', 'Y Lee', '[email protected]');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10002, 'Mouse House', '333 Fromage Lane', 'Columbus', 'OH', '43333', 'USA', 'Jerry Mouse');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10003, 'Wascals', '1 Sunny Place', 'Muncie', 'IN', '42222', 'USA', 'Jim Jones', '[email protected]');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact, cust_email)
VALUES(10004, 'Yosemite Place', '829 Riverside Drive', 'Phoenix', 'AZ', '88888', 'USA', 'Y Sam', '[email protected]');
INSERT INTO customers(cust_id, cust_name, cust_address, cust_city, cust_state, cust_zip, cust_country, cust_contact)
VALUES(10005, 'E Fudd', '4545 53rd Street', 'Chicago', 'IL', '54545', 'USA', 'E Fudd');
########################
# Populate vendors table
########################
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1001,'Anvils R Us','123 Main Street','Southfield','MI','48075', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1002,'LT Supplies','500 Park Street','Anytown','OH','44333', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1003,'ACME','555 High Street','Los Angeles','CA','90046', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1004,'Furball Inc.','1000 5th Avenue','New York','NY','11111', 'USA');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1005,'Jet Set','42 Galaxy Road','London', NULL,'N16 6PS', 'England');
INSERT INTO vendors(vend_id, vend_name, vend_address, vend_city, vend_state, vend_zip, vend_country)
VALUES(1006,'Jouets Et Ours','1 Rue Amusement','Paris', NULL,'45678', 'France');
#########################
# Populate products table
#########################
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV01', 1001, '.5 ton anvil', 5.99, '.5 ton anvil, black, complete with handy hook');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV02', 1001, '1 ton anvil', 9.99, '1 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('ANV03', 1001, '2 ton anvil', 14.99, '2 ton anvil, black, complete with handy hook and carrying case');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('OL1', 1002, 'Oil can', 8.99, 'Oil can, red');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FU1', 1002, 'Fuses', 3.42, '1 dozen, extra long');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SLING', 1003, 'Sling', 4.49, 'Sling, one size fits all');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT1', 1003, 'TNT (1 stick)', 2.50, 'TNT, red, single stick');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('TNT2', 1003, 'TNT (5 sticks)', 10, 'TNT, red, pack of 10 sticks');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FB', 1003, 'Bird seed', 10, 'Large bag (suitable for road runners)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('FC', 1003, 'Carrots', 2.50, 'Carrots (rabbit hunting season only)');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('SAFE', 1003, 'Safe', 50, 'Safe with combination lock');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('DTNTR', 1003, 'Detonator', 13, 'Detonator (plunger powered), fuses not included');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP1000', 1005, 'JetPack 1000', 35, 'JetPack 1000, intended for single use');
INSERT INTO products(prod_id, vend_id, prod_name, prod_price, prod_desc)
VALUES('JP2000', 1005, 'JetPack 2000', 55, 'JetPack 2000, multi-use');
#######################
# Populate orders table
#######################
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20005, '2005-09-01', 10001);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20006, '2005-09-12', 10003);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20007, '2005-09-30', 10004);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20008, '2005-10-03', 10005);
INSERT INTO orders(order_num, order_date, cust_id)
VALUES(20009, '2005-10-08', 10001);
###########################
# Populate orderitems table
###########################
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 1, 'ANV01', 10, 5.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 2, 'ANV02', 3, 9.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 3, 'TNT2', 5, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20005, 4, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20006, 1, 'JP2000', 1, 55);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20007, 1, 'TNT2', 100, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20008, 1, 'FC', 50, 2.50);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 1, 'FB', 1, 10);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 2, 'OL1', 1, 8.99);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 3, 'SLING', 1, 4.49);
INSERT INTO orderitems(order_num, order_item, prod_id, quantity, item_price)
VALUES(20009, 4, 'ANV03', 1, 14.99);
#############################
# Populate productnotes table
#############################
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(101, 'TNT2', '2005-08-17',
'Customer complaint:
Sticks not individually wrapped, too easy to mistakenly detonate all at once.
Recommend individual wrapping.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(102, 'OL1', '2005-08-18',
'Can shipped full, refills not available.
Need to order new can if refill needed.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(103, 'SAFE', '2005-08-18',
'Safe is combination locked, combination not provided with safe.
This is rarely a problem as safes are typically blown up or dropped by customers.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(104, 'FC', '2005-08-19',
'Quantity varies, sold by the sack load.
All guaranteed to be bright and orange, and suitable for use as rabbit bait.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(105, 'TNT2', '2005-08-20',
'Included fuses are short and have been known to detonate too quickly for some customers.
Longer fuses are available (item FU1) and should be recommended.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(106, 'TNT2', '2005-08-22',
'Matches not included, recommend purchase of matches or detonator (item DTNTR).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(107, 'SAFE', '2005-08-23',
'Please note that no returns will be accepted if safe opened using explosives.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(108, 'ANV01', '2005-08-25',
'Multiple customer returns, anvils failing to drop fast enough or falling backwards on purchaser. Recommend that customer considers using heavier anvils.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(109, 'ANV03', '2005-09-01',
'Item is extremely heavy. Designed for dropping, not recommended for use with slings, ropes, pulleys, or tightropes.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(110, 'FC', '2005-09-01',
'Customer complaint: rabbit has been able to detect trap, food apparently less effective now.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(111, 'SLING', '2005-09-02',
'Shipped unassembled, requires common tools (including oversized hammer).'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(112, 'SAFE', '2005-09-02',
'Customer complaint:
Circular hole in safe floor can apparently be easily cut with handsaw.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(113, 'ANV01', '2005-09-05',
'Customer complaint:
Not heavy enough to generate flying stars around head of victim. If being purchased for dropping, recommend ANV02 or ANV03 instead.'
);
INSERT INTO productnotes(note_id, prod_id, note_date, note_text)
VALUES(114, 'SAFE', '2005-09-07',
'Call from individual trapped in safe plummeting to the ground, suggests an escape hatch be added.
Comment forwarded to vendor.'
);