1. MySQL数据库基础
MySQL作为最流行的开源关系型数据库管理系统,其核心架构采用经典的C/S模式。在实际工作中,我经常遇到新手对MySQL的基本概念理解不够深入的问题。让我们从最基础的部分开始,逐步构建完整的MySQL知识体系。
1.1 启动与关闭MySQL服务
启动MySQL客户端时,有几个关键参数需要注意:
- -h参数指定服务器IP,默认127.0.0.1(本地连接)
- -P参数指定端口号,默认3306
- -u参数指定用户名,默认root
- -p参数提示输入密码(建议不要在命令行直接写密码)
bash复制# 启动MySQL客户端
mysql -h127.0.0.1 -P3306 -uroot -p
# 退出客户端
quit
提示:生产环境中,建议使用非root账户连接数据库,并为每个应用创建独立的数据库用户。
1.2 MySQL的核心组件
理解MySQL的架构对后续学习至关重要:
- mysql:客户端程序,负责与用户交互和发送SQL语句
- mysqld:服务端程序,负责处理SQL请求并返回结果
- 存储引擎:负责数据的存储和检索(如InnoDB、MyISAM)
MySQL本质上是一个基于网络的服务,即使在本机连接,也是通过TCP/IP协议通信。这种设计使得远程访问成为可能,同时也带来了性能优化和安全配置的挑战。
1.3 数据库基本操作实战
让我们通过一个完整的例子来演示数据库的基本操作流程:
sql复制-- 创建数据库
CREATE DATABASE IF NOT EXISTS school;
-- 使用数据库
USE school;
-- 创建学生表
CREATE TABLE student (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(32) NOT NULL,
age INT CHECK (age > 0 AND age < 120),
gender ENUM('男','女','其他'),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入数据
INSERT INTO student(name, age, gender) VALUES
('张三', 20, '男'),
('李四', 21, '女'),
('王五', 22, '其他');
-- 查询数据
SELECT * FROM student;
这个例子展示了从创建数据库到操作数据的完整流程。注意以下几点:
- 使用了IF NOT EXISTS避免重复创建
- 设置了主键和自增属性
- 添加了数据校验(CHECK约束)
- 使用了枚举类型限制性别取值
- 设置了默认时间戳
1.4 数据库逻辑结构
MySQL的数据组织采用层级结构:
- 数据库(Database):最高层容器,对应文件系统中的目录
- 表(Table):存储数据的二维结构,对应文件系统中的文件
- 行(Row):表中的一条记录
- 列(Column):表中的一个字段
这种结构使得数据管理更加清晰,也便于实现权限控制和数据隔离。
1.5 SQL语言分类
SQL语句按功能可分为三类:
| 类别 | 全称 | 功能 | 常用命令 |
|---|---|---|---|
| DDL | Data Definition Language | 定义数据结构 | CREATE, ALTER, DROP |
| DML | Data Manipulation Language | 操作数据 | INSERT, SELECT, UPDATE, DELETE |
| DCL | Data Control Language | 权限控制 | GRANT, REVOKE |
在实际开发中,DDL语句通常由DBA执行,而DML语句则是开发人员最常用的。
1.6 存储引擎详解
MySQL支持多种存储引擎,每种引擎有不同特点:
| 引擎 | 事务支持 | 外键支持 | 锁粒度 | 适用场景 |
|---|---|---|---|---|
| InnoDB | 支持 | 支持 | 行锁 | 事务型应用 |
| MyISAM | 不支持 | 不支持 | 表锁 | 读密集型应用 |
| MEMORY | 不支持 | 不支持 | 表锁 | 临时数据缓存 |
查看当前支持的引擎:
sql复制SHOW ENGINES;
经验分享:InnoDB是MySQL 5.5后的默认引擎,除非有特殊需求,否则建议使用InnoDB。我曾经在一个项目中因为不了解引擎特性而错误使用了MyISAM,导致在高并发写入时出现严重性能问题。
2. MySQL库的操作(DDL)
2.1 创建数据库的最佳实践
创建数据库时,字符集和校对规则的选择非常重要:
sql复制CREATE DATABASE IF NOT EXISTS mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
这里有几个关键点:
- utf8mb4是真正的UTF-8编码,支持emoji等4字节字符
- utf8mb4_unicode_ci校对规则支持多语言排序
- IF NOT EXISTS避免重复创建报错
查看字符集和校对规则:
sql复制SHOW VARIABLES LIKE 'character_set_database';
SHOW VARIABLES LIKE 'collation_database';
2.2 数据库的查询与维护
日常维护中常用的查询命令:
sql复制-- 查看所有数据库
SHOW DATABASES;
-- 查看当前使用的数据库
SELECT DATABASE();
-- 查看数据库创建语句
SHOW CREATE DATABASE mydb;
-- 查看数据库连接情况
SHOW PROCESSLIST;
修改数据库字符集(谨慎操作,可能影响现有数据):
sql复制ALTER DATABASE mydb
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
2.3 数据库备份与恢复实战
备份是DBA最重要的日常工作之一。MySQL提供了多种备份方式:
bash复制# 备份单个数据库
mysqldump -uroot -p -B mydb > mydb_backup.sql
# 备份多个表
mysqldump -uroot -p mydb table1 table2 > tables_backup.sql
# 备份所有数据库
mysqldump -uroot -p --all-databases > all_backup.sql
恢复数据库的几种方式:
sql复制-- 方法1:使用source命令
mysql> SOURCE /path/to/mydb_backup.sql;
-- 方法2:命令行直接导入
mysql -uroot -p mydb < mydb_backup.sql
注意事项:备份大型数据库时,可以使用--single-transaction选项获取一致性备份,避免锁表影响业务。
3. MySQL表结构的操作(DDL)
3.1 创建表的进阶技巧
创建表时考虑以下几点能提高性能和可维护性:
sql复制CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
email VARCHAR(100) NOT NULL COMMENT '邮箱',
password_hash CHAR(60) NOT NULL COMMENT '密码哈希',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
这个创建语句包含了多个最佳实践:
- 使用无符号整型作为自增主键
- 为每个字段添加注释
- 设置合理的默认值
- 自动维护创建和更新时间
- 添加唯一约束防止重复数据
- 为查询条件添加索引
- 明确指定存储引擎和字符集
3.2 表结构的维护操作
表结构变更在生产环境中需要谨慎操作:
sql复制-- 添加列
ALTER TABLE users ADD COLUMN
last_login_time DATETIME NULL COMMENT '最后登录时间' AFTER updated_at;
-- 修改列
ALTER TABLE users MODIFY COLUMN
email VARCHAR(150) NOT NULL COMMENT '电子邮箱地址';
-- 重命名列
ALTER TABLE users CHANGE COLUMN
password_hash password CHAR(60) NOT NULL COMMENT '密码哈希';
-- 删除列
ALTER TABLE users DROP COLUMN status;
-- 重命名表
ALTER TABLE users RENAME TO app_users;
避坑指南:在大表上执行ALTER操作可能导致锁表,影响线上服务。可以使用pt-online-schema-change等工具实现在线表结构变更。
4. MySQL的数据类型
4.1 数值类型的选择策略
选择数值类型时需要考虑存储空间和数值范围:
| 类型 | 存储空间 | 有符号范围 | 无符号范围 | 适用场景 |
|---|---|---|---|---|
| TINYINT | 1字节 | -128~127 | 0~255 | 状态码、年龄 |
| SMALLINT | 2字节 | -32768~32767 | 0~65535 | 中等范围数值 |
| INT | 4字节 | -2^31~2^31-1 | 0~2^32-1 | 大多数整数场景 |
| BIGINT | 8字节 | -2^63~2^63-1 | 0~2^64-1 | 大整数、自增ID |
浮点数的选择:
sql复制-- 需要精确计算时使用DECIMAL
CREATE TABLE products (
price DECIMAL(10,2) NOT NULL COMMENT '价格,精确到分'
);
-- 科学计算可以使用FLOAT或DOUBLE
CREATE TABLE scientific_data (
value DOUBLE NOT NULL COMMENT '科学测量值'
);
4.2 字符串类型的性能考量
字符串类型的选择对性能和存储影响很大:
| 类型 | 最大长度 | 存储特点 | 适用场景 |
|---|---|---|---|
| CHAR(N) | 255字符 | 固定长度 | 短且长度固定的字符串(如MD5) |
| VARCHAR(N) | 65535字节 | 可变长度 | 大多数字符串场景 |
| TEXT | 64KB | 可变长度 | 长文本内容 |
| LONGTEXT | 4GB | 可变长度 | 超长文本内容 |
使用建议:
- 明确知道长度固定且较短的字符串用CHAR
- 大多数情况下使用VARCHAR
- 避免过度分配VARCHAR长度,这会消耗内存
- TEXT类型有额外开销,只在必要时使用
4.3 日期时间类型的陷阱
MySQL的日期时间类型容易用错:
sql复制CREATE TABLE events (
event_date DATE COMMENT '日期,无时间',
event_time TIME COMMENT '时间,无日期',
created_at DATETIME COMMENT '日期时间,不自动更新',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '自动更新时间'
);
关键区别:
- DATETIME范围更广(1000-9999年),不自动更新
- TIMESTAMP范围较小(1970-2038年),自动转换时区,可自动更新
- 存储空间:TIMESTAMP(4字节)比DATETIME(8字节)更节省
常见错误:混淆TIMESTAMP和DATETIME导致时间范围问题。曾经遇到一个系统在2038年会出现问题的案例,就是因为大量使用了TIMESTAMP。
4.4 枚举和集合类型的妙用
枚举和集合类型可以简化数据验证:
sql复制CREATE TABLE articles (
status ENUM('draft','published','archived') NOT NULL DEFAULT 'draft',
tags SET('tech','food','travel','fashion') DEFAULT NULL
);
-- 插入数据
INSERT INTO articles (status, tags) VALUES
('published', 'tech,food'),
('draft', 'travel');
使用技巧:
- 枚举适合单选且选项固定的场景
- 集合适合多选且选项固定的场景
- 使用FIND_IN_SET函数查询集合值:
sql复制SELECT * FROM articles WHERE FIND_IN_SET('tech', tags);
5. MySQL表的约束
5.1 数据完整性的保障
约束是保证数据完整性的重要手段:
sql复制CREATE TABLE employees (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
employee_code CHAR(8) NOT NULL COMMENT '员工编号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
department_id INT UNSIGNED NOT NULL COMMENT '部门ID',
email VARCHAR(100) NOT NULL COMMENT '邮箱',
join_date DATE NOT NULL COMMENT '入职日期',
salary DECIMAL(10,2) NOT NULL CHECK (salary > 0),
manager_id INT UNSIGNED COMMENT '上级ID',
PRIMARY KEY (id),
UNIQUE KEY uk_employee_code (employee_code),
UNIQUE KEY uk_email (email),
CONSTRAINT fk_department FOREIGN KEY (department_id)
REFERENCES departments(id) ON DELETE RESTRICT,
CONSTRAINT fk_manager FOREIGN KEY (manager_id)
REFERENCES employees(id) ON DELETE SET NULL
) ENGINE=InnoDB;
这个例子展示了多种约束:
- NOT NULL:禁止NULL值
- PRIMARY KEY:主键约束
- UNIQUE KEY:唯一约束
- CHECK:数据校验(MySQL 8.0+支持)
- FOREIGN KEY:外键约束
5.2 外键约束的实战经验
外键能维护数据一致性,但需谨慎使用:
sql复制-- 添加外键约束
ALTER TABLE orders
ADD CONSTRAINT fk_customer
FOREIGN KEY (customer_id) REFERENCES customers(id)
ON DELETE CASCADE
ON UPDATE CASCADE;
外键动作选项:
- RESTRICT:默认,阻止删除/更新
- CASCADE:级联删除/更新
- SET NULL:设为NULL
- NO ACTION:与RESTRICT类似
经验分享:在高并发系统中,外键可能成为性能瓶颈。一些大型系统会在应用层实现外键逻辑,而不是依赖数据库外键。
6. MySQL表数据的操作(DML)
6.1 高效插入数据的技巧
批量插入比单条插入效率高很多:
sql复制-- 低效方式
INSERT INTO users (name, email) VALUES ('user1', 'user1@example.com');
INSERT INTO users (name, email) VALUES ('user2', 'user2@example.com');
-- 高效方式
INSERT INTO users (name, email) VALUES
('user1', 'user1@example.com'),
('user2', 'user2@example.com'),
('user3', 'user3@example.com');
插入或更新(UPSERT)操作:
sql复制-- ON DUPLICATE KEY UPDATE方式
INSERT INTO users (id, name, email)
VALUES (1, 'user1', 'user1@example.com')
ON DUPLICATE KEY UPDATE name = VALUES(name), email = VALUES(email);
-- REPLACE INTO方式(先删除后插入)
REPLACE INTO users (id, name, email)
VALUES (1, 'user1', 'new_email@example.com');
6.2 查询优化的核心要点
查询是数据库最常用的操作,优化查询性能至关重要:
sql复制-- 基础查询
SELECT id, name, email FROM users WHERE status = 1 ORDER BY created_at DESC LIMIT 10;
-- 多表连接查询
SELECT u.name, o.order_date, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 1 AND o.status = 'completed'
ORDER BY o.order_date DESC;
-- 子查询优化
SELECT u.name
FROM users u
WHERE u.id IN (SELECT DISTINCT user_id FROM orders WHERE amount > 1000);
-- 使用EXISTS替代IN(大数据量时性能更好)
SELECT u.name
FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.amount > 1000);
查询优化建议:
- 只查询需要的列,避免SELECT *
- 合理使用索引
- 避免在WHERE子句中对字段进行函数操作
- 大数据集时考虑分页查询
- 使用EXPLAIN分析查询执行计划
6.3 更新与删除的安全操作
更新和删除操作需要特别注意条件,避免误操作:
sql复制-- 安全更新:先查询确认,再更新
SELECT * FROM users WHERE status = 0;
UPDATE users SET status = 1 WHERE status = 0 AND id IN (1,2,3);
-- 使用事务保证操作原子性
BEGIN;
DELETE FROM log WHERE created_at < '2020-01-01';
COMMIT;
-- 限制删除条数,防止误删
DELETE FROM temp_data ORDER BY id LIMIT 1000;
重要提示:生产环境执行UPDATE或DELETE前,先用SELECT确认影响范围,或者先在测试环境验证。我曾经见过因为没有加WHERE条件导致全表更新的生产事故。
7. MySQL内置函数
7.1 日期函数的实用案例
日期处理是业务系统中的常见需求:
sql复制-- 获取当前日期时间
SELECT NOW(), CURRENT_DATE(), CURRENT_TIME();
-- 日期加减
SELECT DATE_ADD(NOW(), INTERVAL 1 DAY); -- 加1天
SELECT DATE_SUB(NOW(), INTERVAL 1 MONTH); -- 减1个月
-- 日期格式化
SELECT DATE_FORMAT(NOW(), '%Y-%m-%d %H:%i:%s');
-- 计算日期差
SELECT DATEDIFF('2023-12-31', '2023-01-01'); -- 364天
-- 提取日期部分
SELECT YEAR(NOW()), MONTH(NOW()), DAY(NOW());
7.2 字符串处理的高效方法
字符串操作在数据清洗中非常有用:
sql复制-- 字符串连接
SELECT CONCAT(first_name, ' ', last_name) AS full_name FROM users;
-- 字符串截取
SELECT SUBSTRING('MySQL', 2, 3); -- 'ySQ'
-- 字符串替换
SELECT REPLACE('Hello World', 'World', 'MySQL'); -- 'Hello MySQL'
-- 去除空格
SELECT TRIM(' MySQL '), LTRIM(' MySQL'), RTRIM('MySQL ');
-- 字符串长度
SELECT CHAR_LENGTH('中文'), LENGTH('中文'); -- 2, 6(UTF-8)
7.3 数学函数的应用场景
数值计算是数据分析的基础:
sql复制-- 四舍五入
SELECT ROUND(3.14159, 2); -- 3.14
-- 取整
SELECT CEIL(3.14), FLOOR(3.14); -- 4, 3
-- 随机数
SELECT RAND(); -- 0~1之间的随机数
-- 数值格式化
SELECT FORMAT(1234567.89, 2); -- '1,234,567.89'
-- 取模运算
SELECT MOD(10, 3); -- 1
7.4 其他实用函数
一些特殊但很有用的函数:
sql复制-- 条件判断
SELECT IF(1 > 0, 'True', 'False');
SELECT CASE WHEN score >= 90 THEN 'A'
WHEN score >= 80 THEN 'B'
ELSE 'C' END AS grade
FROM students;
-- 空值处理
SELECT IFNULL(NULL, 'default'), COALESCE(NULL, NULL, 'default');
-- 加密函数
SELECT MD5('password'), SHA1('password');
-- 系统信息
SELECT USER(), DATABASE(), VERSION();
8. MySQL索引(重点)
8.1 索引原理深度解析
索引是MySQL性能优化的关键。InnoDB使用B+树索引结构:

B+树的特点:
- 所有数据都存储在叶子节点
- 叶子节点通过指针连接,支持范围查询
- 非叶子节点只存储键值,不存储数据
- 树的高度通常为3-4层,查询效率高
索引类型:
- 聚簇索引(主键索引):叶子节点存储完整数据
- 二级索引(辅助索引):叶子节点存储主键值
8.2 索引创建与优化实践
创建合适的索引能极大提升查询性能:
sql复制-- 创建普通索引
CREATE INDEX idx_name ON users(name);
-- 创建复合索引
CREATE INDEX idx_name_status ON users(name, status);
-- 创建唯一索引
CREATE UNIQUE INDEX uk_email ON users(email);
-- 查看索引
SHOW INDEX FROM users;
-- 删除索引
DROP INDEX idx_name ON users;
索引优化建议:
- 为WHERE、JOIN、ORDER BY子句中的列创建索引
- 遵循最左前缀原则设计复合索引
- 避免过度索引,索引会降低写入性能
- 使用EXPLAIN分析查询是否使用了索引
- 定期使用ANALYZE TABLE更新索引统计信息
性能陷阱:我曾经遇到一个查询性能很差的情况,EXPLAIN显示没有使用索引,原因是查询条件中对索引列使用了函数操作(WHERE DATE(create_time) = '2023-01-01'),改为范围查询(WHERE create_time BETWEEN '2023-01-01 00:00:00' AND '2023-01-01 23:59:59')后性能提升100倍。
9. MySQL事务(重点)
9.1 事务特性与隔离级别
事务的ACID特性:
- 原子性(Atomicity):事务是不可分割的工作单位
- 一致性(Consistency):事务执行前后数据保持一致
- 隔离性(Isolation):并发事务之间互不干扰
- 持久性(Durability):事务提交后永久生效
MySQL的四种隔离级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 最高 |
| READ COMMITTED | 不可能 | 可能 | 可能 | 高 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 | 中 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 低 |
设置隔离级别:
sql复制SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
9.2 事务操作的最佳实践
正确使用事务能保证数据一致性:
sql复制-- 开启事务
START TRANSACTION;
-- 执行SQL操作
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 根据情况提交或回滚
COMMIT;
-- 或
ROLLBACK;
事务使用建议:
- 事务应尽可能短,避免长时间持有锁
- 避免在事务中进行网络或文件IO操作
- 合理设置事务隔离级别
- 处理死锁:重试或设置锁超时
- 使用SAVEPOINT实现部分回滚
9.3 并发问题与锁机制
MySQL通过锁机制解决并发问题:
锁类型:
- 共享锁(S锁):读锁,多个事务可同时持有
- 排他锁(X锁):写锁,独占资源
锁粒度:
- 表锁:MyISAM默认,开销小但并发低
- 行锁:InnoDB默认,开销大但并发高
查看锁情况:
sql复制SHOW ENGINE INNODB STATUS;
实战经验:我曾经遇到一个死锁问题,两个事务分别持有对方需要的锁。解决方案是统一锁获取顺序,或者添加适当的索引减少锁范围。
10. 视图的高级应用
视图可以简化复杂查询并增强安全性:
sql复制-- 创建视图
CREATE VIEW active_users AS
SELECT id, name, email
FROM users
WHERE status = 1 AND deleted = 0;
-- 使用视图
SELECT * FROM active_users WHERE name LIKE '张%';
-- 修改视图
ALTER VIEW active_users AS
SELECT id, name, email, last_login
FROM users
WHERE status = 1 AND deleted = 0;
-- 删除视图
DROP VIEW IF EXISTS active_users;
视图使用场景:
- 简化复杂查询
- 实现行级或列级数据安全
- 提供向后兼容的接口
- 抽象复杂的数据关系
注意事项:视图虽然方便,但过度使用可能导致性能问题,特别是多层嵌套视图时。我曾优化过一个由5层视图组成的查询,改为直接查询基础表后性能提升10倍。
11. 用户与权限管理
11.1 用户管理实战
sql复制-- 创建用户
CREATE USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'secure_password';
-- 修改密码
ALTER USER 'app_user'@'192.168.1.%' IDENTIFIED BY 'new_password';
-- 重命名用户
RENAME USER 'old_user'@'localhost' TO 'new_user'@'localhost';
-- 删除用户
DROP USER 'app_user'@'192.168.1.%';
11.2 精细化的权限控制
sql复制-- 授予权限
GRANT SELECT, INSERT, UPDATE ON db_name.* TO 'app_user'@'192.168.1.%';
-- 授予所有权限
GRANT ALL PRIVILEGES ON db_name.* TO 'admin_user'@'localhost';
-- 撤销权限
REVOKE INSERT ON db_name.* FROM 'app_user'@'192.168.1.%';
-- 查看权限
SHOW GRANTS FOR 'app_user'@'192.168.1.%';
-- 刷新权限
FLUSH PRIVILEGES;
权限管理最佳实践:
- 遵循最小权限原则
- 为不同应用创建独立用户
- 限制用户连接IP范围
- 定期审计用户权限
- 使用角色管理权限(MySQL 8.0+)
12. C/C++访问MySQL
12.1 MySQL C API编程指南
使用C API连接MySQL的基本流程:
c复制#include <mysql/mysql.h>
#include <stdio.h>
int main() {
MYSQL *conn;
MYSQL_RES *res;
MYSQL_ROW row;
// 初始化连接
conn = mysql_init(NULL);
if (conn == NULL) {
fprintf(stderr, "mysql_init() failed\n");
return 1;
}
// 建立连接
if (!mysql_real_connect(conn, "localhost", "user", "password",
"database", 0, NULL, 0)) {
fprintf(stderr, "mysql_real_connect() failed: %s\n",
mysql_error(conn));
mysql_close(conn);
return 1;
}
// 执行查询
if (mysql_query(conn, "SELECT id, name FROM users LIMIT 10")) {
fprintf(stderr, "mysql_query() failed: %s\n",
mysql_error(conn));
mysql_close(conn);
return 1;
}
// 获取结果集
res = mysql_store_result(conn);
if (res == NULL) {
fprintf(stderr, "mysql_store_result() failed: %s\n",
mysql_error(conn));
mysql_close(conn);
return 1;
}
// 处理结果
printf("ID\tName\n");
while ((row = mysql_fetch_row(res)) != NULL) {
printf("%s\t%s\n", row[0], row[1]);
}
// 释放资源
mysql_free_result(res);
mysql_close(conn);
return 0;
}
编译命令:
bash复制gcc -o mysql_test mysql_test.c `mysql_config --cflags --libs`
12.2 预处理语句防SQL注入
使用预处理语句能有效防止SQL注入:
c复制// 准备预处理语句
MYSQL_STMT *stmt = mysql_stmt_init(conn);
const char *query = "INSERT INTO users (name, email) VALUES (?, ?)";
mysql_stmt_prepare(stmt, query, strlen(query));
// 绑定参数
MYSQL_BIND bind[2];
char *name = "test_user";
char *email = "test@example.com";
memset(bind, 0, sizeof(bind));
bind[0].buffer_type = MYSQL_TYPE_STRING;
bind[0].buffer = name;
bind[0].buffer_length = strlen(name);
bind[1].buffer_type = MYSQL_TYPE_STRING;
bind[1].buffer = email;
bind[1].buffer_length = strlen(email);
mysql_stmt_bind_param(stmt, bind);
// 执行语句
mysql_stmt_execute(stmt);
// 释放预处理语句
mysql_stmt_close(stmt);
安全提示:永远不要拼接SQL语句,必须使用预处理语句或转义用户输入。我曾经审计过一个系统,因为直接拼接SQL导致严重的SQL注入漏洞。
在实际项目中,建议使用ORM框架或连接池管理数据库连接,而不是直接使用C API。对于C++项目,可以考虑使用MySQL++或soci等库。