列级约束主要指对列的类型、取值范围、精度等的约束
元组约束指元组中各个字段之间的相互约束,例如某个活动的开始日期小于结束日期。
表级约束指若干元组之间、关系之间的联系的约束。
mysql> create table test(
-> id int primary key,
-> testname char(10) NOT NULL,
-> pro_id int,
-> foreign key(pro_id) references pro(Id) on delete restrict on update restrict
-> );
Query OK, 0 rows affected (0.00 sec)
mysql>
保证记录不重复,其中至少有一列值不重复,用PRIMARY KEY添加约束。
如:
列级约束:
StuNo CHAR(10) PRIMARY KEY
表级约束:
CREATE TABLE db_student
(
StuNo CHAR(10),
StuName VARCHAR(20) NOT NULL,
PRIMARY KEY(StuNo)
)ENGINE=InnoDB;
候选键约束用关键字UNIQUE进行约束。
MySQL中候选键与主键之间存在以下基地那区别:
(1)一个表中只能创建一个主键,但可以定义若干个候选键。
(2)定义主键约束时,系统会自动产生PRIMARY KEY索引,而定义候选键约束时,系统会自动长生UNIQUE索引。
主要指两个表之间的关系,比如课程表开设依赖于教师表,没有教师,就不应该有教师的课。保证数据的正确性。
语法格式如下:
REFERENCES tbl_name [(index_col_name,...)]
放于最后。
在MySQL中,非空约束可以通过在CRETE TABLE或ALTER TABLE语句中的某个列定义后面,加上关键字"NOT NULL"作为限定词。
用户自己定义的约束,比如分数0到100分,不是这范围就会报错。
语法格式如下:
CHECK(expr)
语法格式为:
CONSTRAINT [symbol]
ALTER TABLE <表名> DROP FOREIGN KEY <外键约束名>
ALTER TABLE <表名> DROP PRIMARY KEY
ALTER TABLE <表名> DROP {约束名|候选键字段名}
ALTER TABLE <表名> ADD [CONSTRAINT <约束名>] PRIMARY KEY (主键字段);
ALTER TABLE <表名> ADD [CONSTRAINT <约束名>] FOREIGN KEY (外键字段名) REFERNCES 被参照表(主键字段名);
ALTER TABLE <表名> ADD [CONSTRAINT <约束名>] UNIQUE KEY (主键字段)
mysql> alter table test drop foreign key pro_id;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
-- 删除主键
mysql> alter table test drop primary key;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
-- 添加约束
mysql> alter table test add constraint k_id primary key(id);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
-- 添加外键
mysql> alter table test add constraint fk_proid foreign key(pro_id) references pro(Id);
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql>
触发器是保护表数据的数据库对象,当指定的表发生INSERT(插入数据),UPDATE(修改数据)DELETE(删除数据)时候被触发,进行相应的动作。
语法格式:
CREATE TRIGGER trigger name trigger_time trigger event
ON tbl_name FOR EACH ROW trigger_body
-- 插入数据触发触发器
mysql> create trigger add_test after insert on test for each row set @x='ookk';
Query OK, 0 rows affected (2.05 sec)
mysql> select @x;
+------+
| @x |
+------+
| NULL |
+------+
1 row in set (0.02 sec)
mysql> insert into test values(1,'001',1);
Query OK, 1 row affected (0.00 sec)
mysql> select @x;
+------+
| @x |
+------+
| ookk |
+------+
1 row in set (0.04 sec)
-- 删除数据触发触发器
mysql> create trigger del_test after delete on test for each row set @y=OLD.id;
Query OK, 0 rows affected (2.04 sec)
mysql> select @y;
+------+
| @y |
+------+
| NULL |
+------+
1 row in set (0.02 sec)
mysql> delete from test;
Query OK, 1 row affected (0.00 sec)
mysql> select @y;
+----+
| @y |
+----+
| 1 |
+----+
1 row in set (0.05 sec)
-- ==================================
语法格式:
DROP TRIGGER [IF EXIST][schema_name.]trigger_name
mysql> drop trigger add_test;
Query OK, 0 rows affected (0.00 sec)
mysql>
INSERT、UPDATE、DELETE都会触发。
语法格式:
CREATE USER user [IDENTIFIED BY [PASSWORD]'password'][,user [IDENTIFIED BY [PASSWORD]'password']]...
其中,user的格式为:
'user_name'@'host name'
格式说明:
使用自选的IDENTIFIED BY子句,可以为账户给定一个密码。特别是要在纯文本中指定密码,需忽略PASSWORD关键词。如果不想以明文发送密码,而且知道PASSWORD()函数返回给密码的混编值,则可以指定该混编值,但要加关键字PASSWORD。
查看用户信息:
SELECT user, password from mysql.user;
mysql> select user,password from mysql.user;
+------+-------------------------------------------+
| user | password |
+------+-------------------------------------------+
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| | |
+------+-------------------------------------------+
4 rows in set (2.07 sec)
mysql> create user 'nudt'@'localhost' identified by 'Admin@123';
Query OK, 0 rows affected (2.03 sec)
mysql> select user,password from mysql.user;
+------+-------------------------------------------+
| user | password |
+------+-------------------------------------------+
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| | |
| nudt | *7BB96B4D3E986612D96E53E62DBE9A38AAA40A5A |
+------+-------------------------------------------+
5 rows in set (0.06 sec)
mysql>
语法格式:
DROP USER user[,user_name]...
DROP USER语句用于删除一个或多个MySQL账户,并取消其权限。要使用DROP USER,必须拥有mysql据库的全局CREATE USER权限或DELETE权限。
mysql> drop user 'nudt'@'localhost';
Query OK, 0 rows affected (2.03 sec)
mysql> select user,password from mysql.user;
+------+-------------------------------------------+
| user | password |
+------+-------------------------------------------+
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| | |
+------+-------------------------------------------+
4 rows in set (0.03 sec)
mysql>
语法格式:
RENAME USER old_user TO new_user,[,old_user TO new_user]...
RENAME USER语句用于对原有MySQL账户进行重命名。要使用RENAME USER,必须拥有全局CREATE USER权限或mysql数据库UPDATE权限。如果旧账户不存在或者新账户已存在,则会出现错误。
mysql> create user 'nudt'@'localhost' identified by 'Admin@123';
Query OK, 0 rows affected (0.00 sec)
mysql> rename user 'nudt'@'localhost' to 'NUDT'@'localhost';
Query OK, 0 rows affected (2.06 sec)
mysql> select user,password from mysql.user;
+------+-------------------------------------------+
| user | password |
+------+-------------------------------------------+
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| | |
| NUDT | *7BB96B4D3E986612D96E53E62DBE9A38AAA40A5A |
+------+-------------------------------------------+
5 rows in set (0.04 sec)
mysql>
语法格式:
SET PASSWORD [FOR user]=PASSWORD('newpassword')
说明:
如果不加FOR user,表示修改当前用户的密码。加了FOR user则是修改当前主机上的特定用户的密码,user为用户名。user的值必须以user_name'@'host_name'
的格式给定。
mysql> set password for 'NUDT'@'localhost' = password('Admin@456');
Query OK, 0 rows affected (2.05 sec)
mysql> select user,password from mysql.user;
+------+-------------------------------------------+
| user | password |
+------+-------------------------------------------+
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| root | *81F5E21E35407D884A6CD4A731AEBFB6AF209E1B |
| | |
| NUDT | *C6E4A45A7B6F027D03D9091443891761993F5BFD |
+------+-------------------------------------------+
5 rows in set (0.05 sec)
mysql>
新的SQL用户不允许访问属于其他SQL用户的表,也不能立即创建自己的表,它必须被授权。可以授予的权限有以下几组。
和表中的一个具体列相关。例如,使用UPDATE语句更新表XS学号列的值的权限。
和一个具体表中的所有数据相关。例如,使用SELECT语句查询表XS的所有数据的权限。
和一个具体的数据库中的所有表相关。例如,在已有的XSCJ数据库中创建新表的权限。
和MySQL所有的数据库相关。例如,删除已有的数据库或者创建一个新的数据库的权限。
+++
给某用户授予权限可以使用GRANT语句。使用SHOW GRANTS
语句可以查看当前账户拥有什么权限。
GRANT语法格式:
GRANT priv_type [(column_list)][,priv_type[(column_list)]]... ON [object_type] {tbl_name |*|*.*|db_name.*} TO user [IDENTIFIED BY [PASSWORD]'password'][,user[IDENTIFIED BY[PASSWORD]'password']]...[WITH with_option [with_option]...]
priv_type为权限的名称,如SELECT、UPDATE等,给不同的对象授予权限priv_type的值也不相同。To子句用来设定用户的密码。ON关键字后面给出的是要授予权限的数据库或表名。
授予表权限时,priv_type可以是以下值:
给予用户使用SELECT语句访问特定的表的权力。用户也可以在一个视图公式中包含表。然而,用户必须对视图公式中指定的每个表(或视图)都有SELECT权限。
给予用户使用INSERT语句向一个特定表中添加行的权力。
给予用户使用DELETE语句一个特定表中删除行的权力。
基于用户使用UPDATE语句修改特定表中值的权力。
给予用户创建一个外键来参照特定的表的权力。
给予用户使用特定的名字创建一个表的权力。
给予用户使用ALTER TABLE语句修改表的权力。
给予用户在表上定义索引的权力。
给予用户删除表的权力
表示所有权限名。在授予表权限时,ON关键字后面跟tbl_name,tbl_name为表名或视图名。
只赋予SELECT、INSERT和UPDATE,同时权限的后面需加上列名列表column_list。
表权限适用于一个特定的表。MySQL还支持针对整个数据库的权限。例如,在一个特定的数据库中创建表和视图的权限。
授予数据库权限时,priv_type可以是以下值:
给予用户使用SELECT语句访问特定数据库中所有表和视图的权力。
给予用户使用INSERT语句向特定数据库中所有表添加行的权力
给予用户使用DELETE语句删除特定数据库中所有表的行的权力。
给予用户使用UPDATE语句更新特定数据库中所有表的值的权力。
给予用户创建指向特定的数据库中的表外键的权力。
给予用户为特定的数据库创建存储过程和存储函数等权力
给予用户更新和删除数据库中已有的存储过程和存储函数等权力。
给予用户调用特定数据库存储过程和存储函数的权力。
给予用户锁定特定数据库的已有表的权力。
表示以上所有权限名。
+++
在GRANT语法格式中,授予数据库权限时ON
关键字后面跟*
和db_name.*
。*
表示但钱数据库中所有表;db_name.*
表示某个数据库中的所有表。
最有效率的权限就是用户权限,对于需要授予数据库权限的所有语句,也可以定义在用户权限上。
MySQL授予用户权限时priv_type还可以是以下值:
给予用户创建和删除新用户的权力。
给予用户使用SHOW DATABASES语句查看所有已有的数据库定义的权力,*。*
表示所有数据库的所有表。
mysql> create user 'nudt'@'localhost' identified by 'Admin@123';
Query OK, 0 rows affected (0.00 sec)
-- 授予选择的权限
mysql> grant select on mydata.pro to 'nudt'@'localhost';
Query OK, 0 rows affected (2.05 sec)
-- 授予所有的权限
mysql> grant all on mydata.pro to 'nudt'@'localhost';
Query OK, 0 rows affected (0.00 sec)
GRANT语句的最后可以使用WITH子句。如果指定为WITH GRANT OPTION,则表示TO子句中指定的所有用户都有把自己所拥有的权限授予其他用户的权利,而不管其他用户是否拥有该权限。
WITH子句也可以对一个用户授予使用限制,其中MAX_QUERIES_PER_HOUR count表示每小时可以查询数据库的次数;
MAXCONNECTIONS_PER_HOUR count表示每小时可以连接数据库的次数MAX_UPDATESPER_HOUR count表示每小时可以修改数据库的次数。例如,某人每小时可以查询数据库多少次。MAX_USER_CONNECTIONS count表示同时连接MySQL的最大用户数。count是一个数值,对于前三个指定,count如果为0则表示不起限制作用。
语法格式:
REVOKE priv_type [(column_list)][,priv_type[(column_list)]]...ON {tbl_name |*|*.*|db_name.*} FROM user [,user]...
REVOKE ALL PRIVILEGES, GRANT OPTION FROM user [,user]...
说明:第一种格式用来回收某些特定的权限,第二种格式回收所有该用户的权限。
注意:要使用REVOKE,用户必须拥有MySQL数据库的全局CREATE USER权限或UPDATE权限。
mysql> grant all on mydata.pro to 'nudt'@'localhost' with grant option;
Query OK, 0 rows affected (2.04 sec)
mysql> revoke insert on mydata.pro from 'nudt'@'localhost';
Query OK, 0 rows affected (0.00 sec)
mysql>
事务(Transaction)是用户定义的一个数据库操作序列这些操作要么全做,要么全不做,是一个不可分割的工作单位。
事务和程序是两个概念。
在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。
一个应用程序通常包含多个事务。
事务是恢复和并发控制的基本单位。
事务的ACID特性:
一组更新操作是原子不可分的。
事务必须满足数据库的完整性约束,数据库由一个一致性状态转变到另一个一致性状态。
隔离性要求事务是彼此独立的、隔离的。
持续性也称为永久性(Permanence),是指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
【选择题】事务是数据库进行的基本工作单位。如果一个事务执行成功,则全部更新提交;如果一个事务失败,则已做过的更新被恢复原状,好像整个事务从未有过这些更新,这样保持了数据库处于( )状态。 A. 安全性 B. 一致性 C. 完整性 D. 可靠性 【答案】B 【解释】一致性保证数据库从一个状态到另一个状态
设有两个事务T1和T2,当它们同时读入同一数据并加以修改时,事务T2的提交结果会破坏事务T1提交的结果,由此导致事务T1的修改被丢失。
设有两个事务T1和T2,不可重复读是指事务T1读取数据后,事务T2执行更新操作,使事务T1无法再现前一次读取结果。
设有两个事务T1和T2,读“脏”数据是指,事务T1修某一数据,并将其写回磁盘,事务T2读取同一数据后,事务T1由于某种原因被撤销,这时事务T1已修改过的数据恢复原值,事务T2读到的数据就与数据库中的数据不一致,则事务T2读到的数据就为“脏”数据,即不正确的数据。
封锁是最常用的并发控制技术,它的基本思想是:需要时,事务通过向系统请求对它所希望的数据对象(如数据库中的记录)加锁,以确保它不被非预期改变。
一个锁实质上就是允许或阻止一个事务对一个数据对象的存取特权。
基本的封锁类型有两种:排他锁(Exclusive Lock,X锁)和共享锁(SharedLock,S锁)。
(1)若事务T对数据D加了X锁,则所有别的事务对数据D的锁请求都必须等待直到事务T释放锁。
(2)若事务T对数据D加了S锁,则别的事务还可对数据D请求S锁,而对数据D的X锁请求必须等待直到事务释放锁。
(3)事务执行数据库操作时都要先请求相应的锁。即对读请求S锁,对更新(插入、删除、修改)请求X锁。这个过程一般是由DBMS再执行操作时自动隐含地进行。
(4)事务一直占有获得的锁直到结束(COMMIT或ROLLBACK)时释放。
通常以粒度来描述封锁的数据单元的大小。
DBMS可以决定不同粒度的锁。由最底层的数据元素到最高层的整个数据库,粒度越细,并发性就越大,但软件复杂性和系统开销也就越大。
封锁的事务不重写其他非0级封锁事务的未提交的更新数据。这种状态实际上实用价值不大。
被封锁的事务不允许重写未提交的更新数据。这防止丢失更新的发生。
被封锁的事务既不重写也不读未提交的更新数据。这除了1级封锁的效果外还防止了读脏数据。
被封锁的事务不读未提交的更新数据,不写任何(包括读操作的)未提交数据。
封锁带来的一个重要问题是可能引起“活锁”与“死锁”。
活锁:级别低的事务无法执行。
避免方法:采用先来先服务的策略。
当多个事务请求封锁同一数据对象时,按请求封锁的先后次序对这些事务排队。该数据对象上的锁一旦释放,首先批准申请队列中第一个事务获得锁。
死锁:两个以上事务循环等待被同组中另一事务锁住的数据单元的情形,称为“死锁”。
(1)一次性锁请求
(2)锁请求排序
(3)序列化处理
(4)资源剥夺
对待死锁的另一种办法是不去防止,而让其发生并随时进行检测,一旦检测到系统已经发生了死锁再进行解除处理。
一组事务的一个调度就是它们的基本操作的一种排序。通常,在数据库系统中,可串行性就是并发执行的正确性准则,即当且仅当一组事务的并发执行调度是可串行化的,才认为它们是正确的。
事务划分成如下两个阶段:
在此段期间,对任一数据对象进行任何操作之前,事务都要获得对该对象的一个相应的锁。
一旦事务释放了一个锁,则标明它已进入了此阶段,此后它就不能再请求任何另外的锁。
+++
定理6.1:遵循两段锁协议的事务的任何并发调度都是可串行化的。
+++
数据库中的数据丢失或被破坏可能是由于以下原因
1、计算机硬件故障。由于使用不当或产品质量等原因,计算机硬件可能会出现故障,不能使用。如硬盘损坏会使得存储于其上的数据丢失。
2、软件故障。由于软件设计上的失误或用户使用的不当,软件系统可能会误操作数据引起数据破坏。
3、病毒。破坏性病毒会破坏系统软件、硬件和数据。
4、误操作。如用户误使用了诸如DELETE、UPDATE等命令而引起数据丢失或破坏。
5、自然灾害。如火灾、洪水或地震等,它们会造成极大的破坏,会毁坏计算机系统及其数据。
6、盗窃。一些重要数据可能会遭窃。
用户可以使用SELECT INT…OUTFILE语句把表数据导出到一个文本文件中,并用LOAD DATA…INFILE语句恢复数据。但是这种方法只能导出或导入数据的内容,不包括表的结构,如果表的结构文件损坏,则必须先恢复原来的表的结构。
SELECT INTO...OUTFILE
格式:
SELECT * INTO OUTFILE 'file_name' export_options | DUMPFILE 'file_name'
其中,export_options为:
[FIELDS
[TERMINATED BY 'string']
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES TERMINATED BY 'string']
说明:
这个语句的作用是将表中SELECT语句选中的行写入到一个文件中,file_name是文件的名称。文件默认在服务器主机上创建,并且文件名不能是已经存在的(这可能将原文件覆盖)。如果要将该文件写入到一个特定的位置,则要在文件名前加上具体的路径。在文件中,数据行以一定的形式存放,空值用“\N”表示。
使用OUTFILE时,可以在export_options中加入以下两个自选的子句,它们的作用是决定数据行在文件中存放的格式:
FIELDS子句:在FIELDS子句中有三个亚子句:TERMINATED BY,[OPTIONALLY]ENCLOSED BY和ESCAPED BY。如果指定了FIELDS子句,则这三个亚子句中至少要指定一个。
(1)TERMINATED BY
用来指定字段值之间的符号,例如,"TERMINATED BY’,"指定了逗号作为两个字段值之间的标志。
(2)ENCLOSED BY
子句用来指定包裹文件中字符值的符号,例如,ENCLOSED BY '"'
表示文件中字符值放在双引号之间,若加上关键字OPTIONALLY表示所有的值都放在双引号之间。
(3)ESCAPED BY
子句用来指定转义字符,例如,ESCAPED BY '*'
将*
指定为转义字符,取代\
,如空格将表示为*N
。
LINES子句:在LINES子句中使用TERMINATED BY指定一行结束的标志,如"LINES TERMINATED BY’?"表示一行以“?”作为结束标志。
如果FIELDS和LINES子句都不指定,则默认声明以下子句FIELDS TERMINATED BY '\t' ENCLOSED BY"ESCAPED BY '\\' LINES TERMINATED BY '\n'
如果使用DUMPFILE而不是使用OUTFILE,导出的文件里所有的行都彼此紧挨着放置,值和行之间没有任何标记,成了一个长长的值。
mysql> select * from pro;
+----+------+-------+
| id | pnum | pname |
+----+------+-------+
| 1 | 001 | 产品1 |
| 2 | 002 | 产品2 |
| 3 | 003 | 产品3 |
| 4 | 004 | 产品4 |
+----+------+-------+
4 rows in set (0.05 sec)
mysql> select * from pro into outfile 'c:/1.txt' fields terminated by ',' optionally enclosed by '"' lines terminated by '?';
Query OK, 4 rows affected (0.00 sec)
mysql>
导出的文件:
1,"001","产品1"?2,"002","产品2"?3,"003","产品3"?4,"004","产品4"?
LOAD DATA...INFILE
格式:
LOAD DATA [LOW_PRIORITY | CONCURRENT][LOCAL]INFILE'file_name.txt'
[REPLACE | IGNORE]
INTO TABLE tbl_name
[FIELDS
[TERMINATED BY 'string']
[[OPTIONALLY]ENCLOSED BY 'char']
[ESCAPED BY 'char']
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
[IGNORE number LINES]
[(col_name_or_user_var,...)]
[SET col_name=expr,...]
说明:
LOW_PRIORITY | CONCURRENT
:若指定LOW_PRIORITY,则延迟语句的执行。若指定CONCURRENT,则当LOAD DATA正在执行的时候,其他线程可以同时使用该表的数据。
LOCAL
:若指定了LOCAL,则文件会被客户主机上的客户端读取,并被发送到服务器。文件会被给予一个完整的路径名称,以指定确切的位置。
tb_name
:需要导入数据的表名,该表在数据库中必须存在,表结构必须与导入文件的数据行一致。
REPLACE | IGNORE
:如果指定了REPLACE,则当文件中出现与原有行相同的唯一关键字值时,输入行会替换原有行。如果指定了IGNORE,则把与原有行有相同的唯一关键字值的输入行跳过。
FIELDS
子句:此处的FIELDS子句和SELECT INTO…OUTFILE语句中类似。用于判断字段之间和数据行之间的符号。
LINES
子句:TERMINATED BY亚子句用来指定一行结束的标志。STARTING BY亚子句则指定一个前缀,导入数据行时,忽略行中的该前缀和前缀之前的内容,如果某行不包括该前缀,则整个行被跳过。
GNORE number LINES
:这个选项可以用于忽略文件的前几行。例如,可以使用IGNORE 1 LINES来跳过第一行。
col_name_or_user_var
:如果需要载入一个表的部分列或文件中字段值顺序与表中列的顺序不同,就必须指定一个列清单,其中可以包含列名或用户变量。
mysql> truncate pro;
Query OK, 0 rows affected (2.04 sec)
mysql> select * from pro;
Empty set
mysql> load data infile 'c:/1.txt' into table pro fields terminated by ',' optionally enclosed by '"' lines terminated by '?';
Query OK, 4 rows affected (2.07 sec)
Records: 4 Deleted: 0 Skipped: 0 Warnings: 0
mysql> select * from pro;
+----+------+-------+
| id | pnum | pname |
+----+------+-------+
| 1 | 001 | 产品1 |
| 2 | 002 | 产品2 |
| 3 | 003 | 产品3 |
| 4 | 004 | 产品4 |
+----+------+-------+
4 rows in set (0.06 sec)
mysql>