MySQL必知必會個人備忘錄

這一份筆記以 《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 <<EOF
[mysqld_multi]
mysqld               = /usr/bin/mysqld_safe
mysqladmin           = /usr/bin/mysqladmin

[mysqld3309]
user        = mysql
port        = 3309
socket      = /data/mysql_3309/mysqld.sock
pid-file    = /data/mysql_3309/mysqld.pid
datadir     = /data/mysql_3309/data
basedir     = /usr
bind-address    = 0.0.0.0
character_set_server    = utf8
symbolic-links  = 0
log_error   = /data/mysql_3309/error.log
slow_query_log         = 1
slow_query_log_file    = /data/mysql_3309/slow.log
long_query_time = 2
EOF

[mysqld3310]
user        = mysql
port        = 3310
socket      = /data/mysql_3310/mysqld.sock
pid-file    = /data/mysql_3310/mysqld.pid
datadir     = /data/mysql_3310/data
basedir     = /usr
bind-address    = 0.0.0.0
character_set_server    = utf8
symbolic-links  = 0
log_error   = /data/mysql_3310/error.log
slow_query_log         = 1
slow_query_log_file    = /data/mysql_3310/slow.log
long_query_time = 2
EOF
EOF


# 啓動實例 3309 和 3310, 支持使用連字符語法或用逗號分隔
mysqld_multi --defaults-file=/data/multi.cnf start 3309,3310
# 關閉實例
mysqld_multi --defaults-file=/data/multi.cnf stop 3309-3310
# 查看實例狀態
mysqld_multi --defaults-file=/data/multi.cnf report 3309-3310
!!! mysqld_multi 不支持 !include 或 !includedir

eg. 採用 heartbeat + drbd + mysql 實現mysql高可用雙機熱備方案

未實踐

主從複製

目的:

  • 實時備份
  • 讀寫分離, 主寫, 從讀

eg. MySQL 主從複製

簡單原理

img

  1. Master的IO線程將操作記錄到二進制日誌(Binary log)中
  2. Slave的IO線程會同步拉取Master的二進制日誌並寫入本地中繼日誌(Relay log)
  3. 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. 在業務高峯期, 計算出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 值, 計算出差值, 單位是字節.

  2. 評估1個小時的 redo log 量

    將上述值乘以 60 得到合理的 Redo Log 大小. 因此 innodb_log_file_size 推薦設置成 估算的Redo Log 大小 / 日誌文件數(innodb_log_files_in_group)

  3. 正常關閉 mysql
  4. 修改配置中 innodb_log_file_size 值, 並將數據目錄下的所有 ib_logfile* 文件move走(先不刪, 防止出問題無法啓動)
  5. 啓動 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
    --------------

數據類型

數值數據類型

<u>有符號或無符號</u>

所有數值數據類型(除 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之後是按<u>字符數</u>, 而不是字節數.
  • varchar(20) 可以存儲20個<u>字符</u>
  • 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_timestampon update timestamp
  • 其他情況均會引起不合法報錯
  • ↑ 總結: 最好手動設置 NULL 以免出錯

日期函數:

  • CURRENT_TIMESTAMP, CURRENT_TIMESTAMP()NOW() 的同義詞
  • SYSDATE() 獲取的是函數執行時的時間, NOW()獲取的是執行語句時的<u>開始時間</u>.

二進制數據類型

1559035285382

檢索數據

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 <limit>;        -- 限制返回最多 <limit> 條, 等同 0,<limit>
SELECT * FROM `表名` LIMIT <offset>,<limit>;    -- 略過前 <offset> 條記錄, 返回最多 <limit> 條
SELECT * FROM `表名` LIMIT <limit> OFFSET <offset>;    -- MySQL 5 語法糖

完全限定名

SELECT `表名`.`列名` FROM `數據庫名`.`表名`;        -- 有一些情形需要完全限定名

ORDER 子句

SELECT * FROM `表名` ORDER BY `列名` <dir>;        -- <dir> 默認是 ASC, 可指定 DESC.
SELECT * FROM `表名` ORDER BY `列名1` <dir1>,`列名2` <dir2>;    -- 僅在`列名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:

  • 語法清晰直觀
  • 執行更快
  • <u>可以包含其他SELECT 語句, 得能夠更動態地建立 WHERE 子句</u>

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自己解釋一個,正則表達式庫解釋另一個)。

轉義 . 時, 需使用 \\. 而非 \.

1558600943297

1558600935224

1558600968226

1558601019873

計算字段

計算字段並不實際存在於數據庫表中。計算字段是運行時在 SELECT 語句內創建的。
字段(field) 基本上與列(column)的意思相同,經常互換使用,不過數據庫列一般稱爲列,而術語字段通常用在計算字段的連接上。

AS 別名

別名(alias)是一個字段或值的替換名。

SELECT `列名` AS `別名` FROM `表名`;

別名的其他常見用途:

  • 在實際的表列名包含不符合規定的字符(如空格)時重新命名它
  • 在原來的名字含混或容易誤解時擴充它,等等。

算數運算

1558601948352

圓括號可用來區分優先順序。

函數

函數沒有SQL的可移植性強, 幾乎每種主要的DBMS的實現都支持其他實現不支持的函數,而且有時差異還很大。

字符串處理

concat() 字符串拼接

SELECT concat(`列名1`, '(', `列名2`, ')') FROM `表名`;    -- 組成: `列名1`(`列名2`)
MySQL的不同之處 多數DBMS使用 + 或 || 來實現拼接,MySQL則使用 Concat() 函數來實現。當把SQL語句轉換成MySQL語句時一定要把這個區別銘記在心

1558602282966

  • 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開始

日期和時間

1558603084142

MySQL 日期格式固定爲: yyyy-mm-dd, 無論是插入,更新,或WHERE字句過濾.

補充

函數 說明 示例
from_unixtime() 將時間戳轉爲日期 from_unixtime(1559001600)
unix_timestamp() 將指定日期或日期字符串轉換爲時間戳 unix_timestamp(Now())

數值處理

1558603490365

聚集函數

1558603562124

標準偏差 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 子句中嵌套了分組,數據將在最後規定的分組上進行彙總。換句話說,在建立分組時,指定的所有列都一起計算(所以不能從個別的列取回數據)。
  • <u>GROUP BY 子句中列出的每個列都必須是檢索列或有效的表達式(但不能是聚集函數)。如果在 SELECT 中使用表達式,則必須在GROUP BY 子句中指定相同的表達式。不能使用別名。</u>
  • 除聚集計算語句外, SELECT 語句中的每個列都必須在 GROUP BY 子句中給出。
  • 如果分組列中具有 NULL 值,則 NULL 將作爲一個分組返回。如果列中有多行 NULL 值,它們將分爲一組。
  • GROUP BY 子句必須出現在 WHERE 子句之後, ORDER BY 子句之前。

1558604730593

??

過濾分組

HAVING 過濾的是分組

WHERE 過濾的是行
  • HAVING 可以使用別名

HAVING 和 WHERE 的差別

這裏有另一種理解方法, WHERE 在數據分組前進行過濾, HAVING 在數據分組後進行過濾。這是一個重要的區別, WHERE 排除的行不包括在分組中。這可能會改變計算值,從而影響 HAVING 子句中基於這些值過濾掉的分組。

子查詢

根據子查詢執行的次數, 對子查詢進行分類:

  1. 非關聯子查詢

    查詢不依賴外層(即與主查詢無關), 該子查詢只需執行一次, 得到的數據結果可以作爲外層查詢的條件直接使用.

  2. 關聯子查詢

    查詢時依賴外層(用到外層表數據, 與主查詢相關), 因此每次外層查詢都要根據外層查詢結果再進行子查詢, 該子查詢需要執行多次.

在實際使用時由於性能的限制,不能嵌套太多的子查詢。

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

    必須與比較操作符一起使用, 與子查詢中<u>任意值</u>比較

    SELECT * FROM A WHERE A.cc > ANY (SELECT cc FROM B);
  • SOME

    是ANY的別名, 等價於 ANY, 一般常用 ANY

  • ALL

    必須與比較操作符一起使用, 與子查詢中<u>所有值</u>比較

    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

1558670169544

聯結是一種機制,用來在一條 SELECT語句中關聯表,因此稱之爲聯結。

使用特殊的語法,可以聯結多個表返回一組輸出,聯結在運行時關聯表中正確的行。

使用聯結的要點:

  • 注意所使用的聯結類型。一般我們使用內部聯結,但使用外部聯結也是有效的。
  • 保證使用正確的聯結條件,否則將返回不正確的數據。
  • 應該總是提供聯結條件,否則會得出笛卡兒積。
  • 在一個聯結中可以包含多個表,甚至對於每個聯結可以採用不同的聯結類型。雖然這樣做是合法的,一般也很有用,但應該在一起測試它們前,分別測試每個聯結。這將使故障排除更爲簡單。

<u>表別名</u>只在查詢執行中使用。與列別名不一樣,表別名返回到客戶機。

<u>完全限定列名</u> 在引用的列可能出現二義性時,必須使用完全限定列名(用一個點分隔的表名和列名)。如果引用一個沒有用表名限制的具有二義性的列名,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;

根據獲取到的結果集的範圍將連接進行分類:

  • <u>內連接</u>: 取多個表之間滿足條件的數據行(交集)

    隱式的內連接: 不寫 INNER JOIN

    顯式的內連接: 寫 INNER JOIN

  • <u>外連接</u>: 除了取滿足條件的交集外, 還會取某一方不滿足條件的記錄.

    左外連接 LEFT OUTER JOIN

    右外連接 RIGHT OUTER JOIN

    全外連接 FULL OUTER JOIN

    書寫時 OUTER 可以忽略

  • <u>交叉連接</u>: 笛卡爾積(cartesian product)(所有集合的所有組合).

    CROSS JOIN

    返回記錄的條數是每個表的行數的乘積.

根據連接時的測試條件, 將連接進行分類:

  • <u>等值連接</u>: 連接條件是等號
  • <u>非等值連接</u>: 連接條件是非等號

當自身與自身進行連接時, 稱爲<u>自連接</u>.

內部聯結 INNER JOIN

<u>內部聯結</u>即上面的<u>等值聯結</u>, 它基於兩個表之間的相等測試。

SELECT vendors.vend_id,vend_name,prod_name,prod_price FROM vendors INNER JOIN products ON vendors.vend_id = products.vend_id;

<u>使用哪種語法?</u> ANSI SQL規範首選 INNER JOIN 語法。此外,儘管使用 WHERE 子句定義聯結的確比較簡單,但是使用明確的聯結語法能夠確保不會忘記聯結條件,有時候這樣做也能影響性能。

<u>性能考慮</u> 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';

<u>用自聯結而不用子查詢</u> 自聯結通常作爲外部語句用來替代從相同表中檢索數據時使用的子查詢語句。雖然最終的結果是相同的,但有時候處理聯結遠比處理子查詢快得多。應該試一下兩種方法,以確定哪一種的性能更好

外部聯結 OUTER JOIN

<u>外部聯結</u>: 聯結包含了那些在相關表中沒有關聯行的行。

許多聯結將一個表中的行與另一個表中的行相關聯。但有時候會需
要包含沒有關聯行的那些行。例如,可能需要使用聯結來完成以下工作:

  • 對每個客戶下了多少訂單進行計數,包括那些至今尚未下訂單的
    客戶;
  • 列出所有產品以及訂購數量,包括沒有人訂購的產品;
  • 計算平均銷售規模,包括那些至今尚未下訂單的客戶。
-- 使用 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 語法時,必須使用 RIGHTLEFT 關鍵字指定包括其所有行的表( RIGHT 指出的是 OUTER JOIN 右邊的表,而 LEFT指出的是 OUTER JOIN 左邊的表)。

  • LEFT OUTER JOIN 左外聯結, 包含座標的全部記錄, 若無對應的右邊記錄, 則其值爲 NULL
  • RIGHT OUTER JOIN
OUTER 關鍵字可以省略不寫.

<u>外部聯結的類型</u> 存在兩種基本的外部聯結形式:左外部聯結和右外部聯結。它們之間的唯一差別是所關聯的表的順序不同。換句話說,左外部聯結可通過顛倒 FROM 或 WHERE 子句中表的順序轉換爲右外部聯結。因此,兩種類型的外部聯結可互換使用,而究竟使用哪一種純粹是根據方便而定。

組合查詢 UNION

MySQL也允許執行多個查詢(多條 SELECT 語句),並將結果作爲單個查詢結果集返回。這些組合查詢通常稱爲並(union)或複合查詢(compound query)。

<u>組合查詢和多個 WHERE 條件</u> 多數情況下,組合相同表的兩個查詢完成的工作與具有多個 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;
<u>!!不要在導入數據時使用 FULLTEXT</u>

更新索引要花時間,雖然不是很多,但畢竟要花時間。如果正在導入數據到一個新表,此時不應該啓用 FULLTEXT 索引。應該首先導入所有數據,然後再修改表,定義 FULLTEXT 。這樣有助於更快地導入數據(而且使索引數據的總時間小於在導入每行時分別進行索引所需的總時間)。

進行全文本搜索 MATCH…AGAINST…

全文本搜索返回的結果默認排序是按照關聯程度最高的排在最前面

-- 針對指定的列進行搜索
SELECT * FROM `表名` WHERE Match(`列名`) Against('搜索詞');
Match(列名) Against('搜索詞') 實際上是計算出一個代表關聯程度的數值, 該數值可以在 SELECT 中直接查看.
?? <u>使用完整的 Match() 說明</u> 傳遞給 Match() 的值必須與FULLTEXT() 定義中的相同。如果指定多個列,則必須列出它們(而且次序正確)。
<u>!!搜索不區分大小寫</u> 除非使用 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再次進行全文本搜索,這次不僅使用原來的條件,而且還使用所有有用的詞。

利用查詢擴展,能找出可能相關的結果,即使它們並不精確包含所查找的詞。

<u>行越多越好</u> 表中的行越多(這些行中的文本就越多),使用查詢擴展返回的結果越好。

布爾文本搜索

布爾方式(booleanmode)

  • 要匹配的詞;
  • 要排斥的詞(如果某行包含這個詞,則不返回該行,即使它包含
    其他指定的詞也是如此);
  • 排列提示(指定某些詞比其他詞更重要,更重要的詞等級更高);
  • 表達式分組;
  • 另外一些內容。
<u>即使沒有 FULLTEXT 索引也可以使用</u> 布爾方式不同於迄今爲止使用的全文本搜索語法的地方在於,即使沒有定義
FULLTEXT 索引,也可以使用它。但這是一種非常緩慢的操作(其性能將隨着數據量的增加而降低)。
SELECT note_id,note_text 
FROM productnotes 
WHERE match(note_text) against('heavy -rope*' IN BOOLEAN MODE);

說明:

  • 匹配 heavy
  • 排除 rope 開頭的詞

1558685103097

插入數據 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`;

<u>插入時省略列需滿足以下任一條件</u>:

  • 該列定義爲允許 NULL 值(無值或空值)。
  • 在表定義中給出默認值。這表示如果不給出值,將使用默認值。

<u>降低插入優先級</u> INSERT LOW PRIORITY INTO

LOW PRIORITY 同樣適用於 UPDATEDELETE 語句

提高 INSERT 的性能 一次插入多條記錄可以提高數據庫處理的性能,因爲MySQL用單條 INSERT 語句處理多個插入比使用多條 INSERT語句快。

<u>INSERT SELECT 中的列名</u> 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 ...;

<u>IGNORE 關鍵字</u> 如果用 UPDATE 語句更新多行,並且在更新這些行中的一行或多行時出一個現錯誤,則整個 UPDATE 操作被取消(錯誤發生前更新的所有行被恢復到它們原來的值)。爲即使是發生錯誤,也繼續進行更新,可使用 IGNORE 關鍵字

刪除數據 DELETE

DELETE FROM `表名`
WHERE ...;

<u>刪除表的內容而不是表</u> DELETE 語句從表中刪除行,甚至是刪除表中所有行。但是, DELETE 不刪除表本身。

<u>更快的刪除</u> 如果想從表中刪除所有行,不要使用 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 值的列不接受該列沒有值的行,

<u>理解 NULL</u> 不要把 NULL 值與空串相混淆。 NULL 值是沒有值,它不是空串。如果指定 '' (兩個單引號,其間沒有字符),這在 NOT NULL 列中是允許的。空串是一個有效的值,它不是無值。 NULL 值用關鍵字 NULL 而不是空串指定。

主鍵和 NULL 值 主鍵爲其值唯一標識表中每個行的列。<u>主鍵中只能使用不允許 NULL 值的列</u>。允許 NULL 值的
列不能作爲唯一標識。

AUTO_INCREMENT

  • 每個表只允許一個 AUTO_INCREMENT 列,而且它必須被索引(如,通過使它成爲主鍵)
  • 在 INSERT 語句中指定一個值,只要它是唯一的(至今尚未使用過)即可,該值將被用來替代自動生成的值。後續的增量將開始使用該手工插入的值。
  • last_insert_id() 函數返回最後一個 AUTO_INCREMENT 值.

    eg. 增加一個新訂單

    1. orders 表中創建一行
    2. 使用 last_insert_id() 獲取自動生成的 order_num
    3. 在 orderitms 表中對訂購的每項物品創建一行。 order_num 在 orderitems 表中與訂單細節一起存儲。

DEFAULT

  • 使用當前時間作爲默認值

    CREATE TABLE `表名`(
        ...,
        `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
    );
  • 許多數據庫開發人員使用默認值而不是 NULL 列,特別是對用於計算或數據分組的列更是如此。

ENGINE

  • InnoDB 是一個可靠的事務處理引擎,它不支持全文本搜索;
  • MEMORY 在功能等同於 MyISAM ,但由於數據存儲在內存(不是磁盤)中,速度很快(特別適合於臨時表);
  • MyISAM 是一個性能極高的引擎,它支持全文本搜索(參見第18章),但不支持事務處理。

<u>外鍵不能跨引擎</u> 混用引擎類型有一個大缺陷。外鍵(用於強制實施引用完整性,如第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 語句從舊錶複製數據到新表。如果有必要,可使用轉換函數和計算字段;
  • 檢驗包含所需數據的新表;
  • 重命名舊錶(如果確定,可以刪除它);
  • 用舊錶原來的名字重命名新表;
  • 根據需要,重新創建觸發器、存儲過程、索引和外鍵。

<u>小心使用 ALTER TABLE</u> 使用 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 全文索引:對文本的內容進行分詞,進行搜索

<u>術語</u>

  • 索引合併:使用多個單列索引組合查詢搜索
  • 覆蓋索引:select的數據列只用從索引中就能夠取得,不必讀取數據行,換句話說查詢列要被所建的索引覆蓋

組合索引

  • 同時搜索多個條件時,組合索引的性能效率好過於多個單一索引合併

SQL Explain

備註: 此處將僅作爲存檔, 後續更新將在 這裏 中處理. 2019年10月18日15:53:53

1559124811057

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 <id>
  • 關閉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語句。

<u>創建可重用的視圖</u>

創建不受特定數據限制的視圖是一種好辦法。例如,上面創建的視圖返回生產所有產品的客戶而不僅僅是 生產TNT2 的客戶。擴展視圖的範圍不僅使得它能被重用,而且甚至更有用。這樣做不需要創建和維護多個類似視圖。

<u>將視圖用於檢索</u>

一般,應該將視圖用於檢索( 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

<u>參數的數據類型</u>

存儲過程的參數允許的數據類型與表中使用的數據類型相同。

!!! 記錄集不是允許的類型,因此,不能通過一個參數返回多個行和列。

一個簡單的示例: 計算商品的最低、最高、平均價格

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;

變量

<u>變量(variable)</u>

內存中一個特定的位置,用來臨時存儲數據。

變量名不區分大小寫

用戶變量

@變量名, 僅對當前連接有效

可以使用 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;
參數只能使用<u>用戶變量</u>來傳遞

智能存儲過程

在存儲過程中包含:

  • 業務規則
  • 智能處理
-- 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 語句,而是被該語句檢索出來的結果集。在存儲了遊標之後,應用程序可以根據需要滾動或瀏覽其中的數據。

<u>只能用於存儲過程</u> 不像多數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 操作執行的觸發器,則應該定義兩個觸發器。

<u>僅支持表</u> 只有表才支持觸發器,視圖不支持(臨時表也不支持)

-- 創建觸發器
CREATE TRIGGER `觸發器名` 
AFTER|BEFORE 
INSERT|UPDATE|DELETE 
ON `表名`
FOR EACH ROW
...

-- 刪除觸發器
DROP TRIGGER `觸發器名`;

<u>BEFORE 或 AFTER ?</u> 通常,將 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...

數據庫事務隔離級別:

  1. read-uncommited 讀未提交

    存在所有問題, 最低的隔離級別。一個事務可以讀取另一個事務並未提交的更新結果。

  2. read-commited 讀已提交

    解決"髒讀", 大部分數據庫採用的默認隔離級別。一個事務的更新操作結果只有在該事務提交之後,另一個事務纔可以的讀取到同一筆數據更新後的結果。

  3. repeatable-read 可重複讀

    解決"不可重複讀", 整個事務過程中,對同一筆數據的讀取結果是相同的,不管其他事務是否在對共享數據進行更新,也不管更新提交與否。

  4. serializable 序列化

    解決"幻讀", 最高隔離級別。所有事務操作依次順序執行。注意這會導致併發度下降,性能最差。通常會用其他併發級別加上相應的併發鎖機制來取代它。

  • 不可重複讀
  • 可重複讀
  • 幻讀

MySQL 默認級別是 repeatable-read


術語

  • 事務( transaction )指一組SQL語句;
  • 回退( rollback )指撤銷指定SQL語句的過程;
  • 提交( commit )指將未存儲的SQL語句結果寫入數據庫表;
  • 保留點( savepoint )指事務處理中設置的臨時佔位符(place-holder),你可以對它發佈回退(與回退整個事務處理不同)。
-- 標識事務開始
START TRANSACTION;

-- ROLLBACK;
-- COMMIT;

<u>哪些語句可以回退?</u> 事務處理用來管理 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;

<u>SELECT 的其他 COLLATE 子句</u> 除了這裏看到的在 ORDER BY子句 中使用以外, COLLATE 還可以用於 GROUP BY 、 HAVING 、聚集函數、別名等。

串可以在字符集之間進行轉換。爲此,使用 Cast() 或 Convert ()函數

安全管理

用戶應該對他們需要的數據具有適當的訪問權,既不能多也不能少

<u>不要使用 root</u> 應該嚴肅對待 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;
  • 特定的列;
  • 特定的存儲過程。

1559027891075

數據庫維護

數據庫備份

  • msyqldump 程序
  • BACKUP TABLESELECT 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(完美範式)

這裏的

  • 部分依賴 也稱爲 部分函數依賴
  • 傳遞依賴 也稱爲 傳遞函數依賴

通常要求: <u>3NF</u>

根據實際情況, 必須時可以新增冗餘字段來提高查詢效率, 需要權衡.

範式的嚴格程度是依次遞增, 且高級別範式肯定是滿足低級別範式的.

存儲引擎

該部分主要來自: 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
文件能否移動 能,一張表就對應.frmMYDMYI3個文件 否,因爲關聯的還有data下的其它文件
記錄存儲順序 按記錄插入順序保存 按主鍵大小有序插入
空間碎片(刪除記錄並flush table 表名之後,表文件大小不變) 產生。定時整理:使用命令optimize table 表名實現 不產生
事務 不支持 支持
外鍵 不支持 支持
鎖支持(鎖是避免資源爭用的一個機制,MySQL鎖對用戶幾乎是透明的) 表級鎖定 行級鎖定、表級鎖定,鎖定力度小併發能力高
鎖擴展

表級鎖(table-level lock):lock tables <table_name1>,<table_name2>... read/writeunlock tables <table_name1>,<table_name2>...。其中read是共享鎖,一旦鎖定任何客戶端都不可讀;write是獨佔/寫鎖,只有加鎖的客戶端可讀可寫,其他客戶端既不可讀也不可寫。鎖定的是一張表或幾張表。

行級鎖(row-level lock):鎖定的是一行或幾行記錄。共享鎖:select * from <table_name> where <條件> LOCK IN SHARE MODE;,對查詢的記錄增加共享鎖;select * from <table_name> where <條件> FOR UPDATE;,對查詢的記錄增加排他鎖。這裏值得注意的是:innodb的行鎖,其實是一個子範圍鎖,依據條件鎖定部分範圍,而不是就映射到具體的行上,因此還有一個學名:間隙鎖。比如select * from stu where id < 20 LOCK IN SHARE MODE會鎖定id20左右以下的範圍,你可能無法插入id1822的一條新紀錄。

課程數據

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.'
);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章