ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MySQL、Oracle、SQL Server三大数据库语法差异详解与踩坑指南

MySQL、Oracle、SQL Server三大数据库语法差异详解与踩坑指南 做数据库开发和运维这些年我身边几乎每个人都喊过同一句话“这三个库的SQL怎么又不一样” 上午在MySQL里写得好好的分页语句拿到Oracle直接报错下午把一段SQL Server的日期处理搬到MySQL结果日期格式全乱。要说语法差异网上一搜一大把但大部分是零散笔记今天我想系统性地把MySQL、Oracle、SQL Server这三大数据库的语法区别梳理一遍结合我自己的踩坑经历从数据类型到DML、从分页到存储过程把最实用、最容易被忽略的差异点一次性讲透。这篇文章适合三类人一是刚入行需要同时接触多个数据库的开发二是做数据迁移或ETL任务的工程师三是经常写跨库工具脚本的人。读完之后不敢说你能闭眼写三库通用SQL但至少看到报错能第一时间反应过来是哪里的语法不兼容。1. 先搞明白一件事三大数据库为什么各自为政1.1 出身和生态决定了语法走向很多新手不理解SQL明明有国际标准为什么三大数据库还是各写各的。说白了SQL标准只是给了一个基础框架各家在实现时都有自己的历史包袱和商业考量。MySQL出生在开源社区早期主打轻量、快速、易用语法上强调短平快能省就省LIMIT直接加在查询后面写起来非常直觉化。Oracle是商业数据库里的老大哥企业级应用几十年的积累PL/SQL语言极其强大它更追求严谨和可扩展性所以语法偏“重型”处处体现数据字典、角色权限、事务控制的体系化设计。SQL Server则是微软生态的宠儿T-SQL在易用性上做得最好和Windows开发体系无缝衔接很多语法细节比如GETDATE()、ISNULL()、号拼接字符串都和C#语言风格一脉相承对从应用开发转过来的程序员特别友好。这三个库的“性格”差异决定了它们的SQL语法必然走向不同分支。MySQL的朴实、Oracle的严谨、SQL Server的易用都直接反映在每一句SQL的写法上。1.2 标准SQL的“标准”其实有限很多人问那我全部写标准SQL不就行了理论上可以但实际做项目的人都明白标准SQL覆盖的只是最基础的SELECT、INSERT、UPDATE、DELETE、WHERE、JOIN等核心能力。一旦涉及分页、字符串处理、日期计算、自增字段、批量插入这些日常高频操作三大数据库就会各走各路。比如标准SQL里规定了||作为字符串连接符Oracle和PostgreSQL都遵守了但MySQL默认用CONCAT()函数SQL Server默认用号三者直接对不上。标准SQL里没有定义类似LIMIT的通用分页子句现代SQL标准后来加了OFFSET...FETCH但Oracle在12c之前一直不支持所以MySQL用LIMIT、SQL Server用TOP或OFFSET FETCH、Oracle用ROWNUM或FETCH FIRST各有各的祖传写法。1.3 一张表看懂三库性格对比维度MySQLOracleSQL Server语言继承MySQL原生SQLPL/SQLT-SQL设计理念轻量简洁严谨厚重易用友好典型场景互联网应用、读写频繁银行、电信等核心交易系统企业ERP、微软生态自增方案AUTO_INCREMENTSEQUENCE序列IDENTITY分页习惯LIMITROWNUM/OFFSETTOP/OFFSET FETCH字符串拼接CONCAT()||日期处理DATE_FORMATTO_CHAR/TO_DATECONVERT这张表基本就是全文的浓缩版后面每一个差异点我都会展开讲原理和使用细节。2. 建表与数据类型第一个分岔路口2.1 字符串类型VARCHAR、VARCHAR2和NVARCHAR的距离这是建表时最容易被坑的地方。MySQL里最常用的字符串类型是VARCHAR(n)和CHAR(n)n代表字符数不是字节数。也就是说VARCHAR(10)在utf8mb4字符集下最多能存10个汉字即使一个汉字占3个字节也无所谓因为MySQL按字符计数。Oracle里没有VARCHAR只有VARCHAR2(n)而且这个n默认是字节数不是字符数。很多从MySQL转过来的人建表时写VARCHAR2(10)以为能存10个汉字结果插入中文直接报“ORA-12899: value too large for column”就是因为10个字节在UTF-8下只能放3个汉字。解决办法是写成VARCHAR2(10 CHAR)显式指定按字符计数或者在数据库层面设置NLS_LENGTH_SEMANTICSCHAR。Oracle新版虽然也支持VARCHAR但官方文档明确建议始终使用VARCHAR2这一点我建议直接照做。SQL Server则是另一个流派它有VARCHAR(n)和NVARCHAR(n)两种。VARCHAR按非Unicode字符存储一个英文字符1字节一个中文占2字节取决于代码页NVARCHAR按Unicode存储所有字符统一2字节最大长度为4000字符。实际项目里我强烈建议SQL Server的表尽量用NVARCHAR否则接口对接时一旦传入中文或特殊符号很容易出现乱码或溢出。2.2 数字与日期精确到小数点背后的理念差异数字类型方面MySQL用的是TINYINT/SMALLINT/INT/BIGINT加DECIMAL(p,s)、FLOAT/DOUBLE的分类方式Oracle则是统一用NUMBER(p,s)搞定所有数字场景无论是整数还是小数都收在这个类型里NUMBER的精度范围可以从NUMBER(1)到NUMBER(38)SQL Server则和MySQL类似有INT/BIGINT/SMALLINT/TINYINT、DECIMAL(p,s)、NUMERIC(p,s)、FLOAT/REAL它的DECIMAL和NUMERIC基本等价。日期时间类型差异更大。MySQL有DATE、TIME、DATETIME、TIMESTAMP四种常用类型DATETIME存的是普通时间TIMESTAMP会自动跟随时区且在2038年有溢出问题。Oracle的日期核心是DATE精确到秒和TIMESTAMP精确到纳秒支持时区而且Oracle的DATE本身就带时分秒和MySQL的DATE只存年月日完全不同这个不特别注意的话直接通过SYSDATE对比时间会把人绕晕。SQL Server有DATE纯年月日和DATETIME精确到3.33毫秒以及DATETIME2精确到100纳秒、DATETIMEOFFSET带时区偏移其中DATETIME2是后来自主推荐使用的。说到数字还有一个SQL Server特有的大坑INT除法永远返回整数。写SELECT 5/2在MySQL和Oracle里结果都是2.5在SQL Server里结果是2因为两个整数相除会走整数除法。如果业务里必须保留小数得写成SELECT 5.0/2或者SELECT CAST(5 AS DECIMAL(10,2))/2。这种隐含转换的坑最容易在报表计算里冒出来。2.3 自增字段AUTO_INCREMENT、IDENTITY与SEQUENCE这是建表时最直观的语法分叉。MySQL的自增最简单字段定义时加AUTO_INCREMENT然后指定这个字段是主键即可CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) );SQL Server用的是IDENTITY(起始值, 步长)CREATE TABLE user ( id INT IDENTITY(1,1) PRIMARY KEY, name NVARCHAR(50) );Oracle呢它没有自增关键字老版本必须用序列加触发器来实现所谓的自增ID12c之后虽然支持IDENTITY列但很多老项目还是沿用序列的习惯CREATE SEQUENCE seq_user_id START WITH 1 INCREMENT BY 1; -- 插入时手动取序列值 INSERT INTO user (id, name) VALUES (seq_user_id.NEXTVAL, 张三); -- 或者用触发器在插入前自动取序列 CREATE OR REPLACE TRIGGER trg_user_id BEFORE INSERT ON user FOR EACH ROW BEGIN SELECT seq_user_id.NEXTVAL INTO :NEW.id FROM DUAL; END;我遇到过不少从MySQL迁到Oracle的项目程序里用了SELECT LAST_INSERT_ID()去拿新插入记录的ID在Oracle里根本没有这个函数白折腾了好几天。要拿序列当前值Oracle用的是seq_user_id.CURRVAL但前提是当前会话已经调用过NEXTVAL否则会报“ORA-08002: sequence CURRVAL is not yet defined in this session”这也是一个常见坑。2.4 默认值与NULL的底层分歧默认值和NULL的处理三库的哲学也不同。MySQL和SQL Server都支持在字段定义处直接写DEFAULT 0、DEFAULT 无等Oracle也支持DEFAULT但12c之前对默认值的处理有个限制如果插入时没有指定该列并且默认值是一个序列的NEXTVAL或者复杂表达式会有一些怪问题需要靠触发器绕过去。NULL的处理更是分歧明显。MySQL里空字符串和NULL是两个概念字符串列插入和NULL在COUNT、GROUP BY里表现完全不同。Oracle更极端它的VARCHAR2空字符串会被当成NULL也就是说INSERT INTO t(name) VALUES ()之后你查出来的是NULL而不是空字符串这一度让无数从其他库迁移过来的开发懵圈。SQL Server的分界相对清楚一些就是NULL就是NULL但CONCAT有特殊处理后面函数篇我会讲到。这里提醒新手一个反直觉的点在Oracle里判断一个字段是否有值WHERE col ! 永远查不出任何行因为col ! 会被推导为col ! NULL结果是未知行被过滤掉了。正确写法必须是WHERE col IS NULL或IS NOT NULL。3. 查询语法分页、排序与字符串处理的三重体验3.1 分页LIMIT、ROWNUM、OFFSET FETCH分页是语法差异最明显、报错率最高的操作。MySQL从入门就是LIMIT一统天下SELECT * FROM employees ORDER BY emp_id LIMIT 10 OFFSET 20; -- 也可以简写成 LIMIT 20, 10这个写法简单直观LIMIT offset, count先偏移后取数量从0开始算偏移。Oracle在12c之前根本没有LIMIT传统写法是用ROWNUM伪列。但ROWNUM有个著名陷阱直接在WHERE里写WHERE ROWNUM 10是取不到数据的因为ROWNUM是在结果集产生过程中逐行分配的查询出来的第一行永远是1大于10的条件根本轮不到后续行。正确写法通常要套一层子查询SELECT * FROM ( SELECT e.*, ROWNUM rn FROM ( SELECT * FROM employees ORDER BY emp_id ) e WHERE ROWNUM 30 ) WHERE rn 20;注意内层必须先排序再取ROWNUM 30外层再过滤rn 20顺序错了数据就不对。Oracle 12c之后引入了OFFSET...FETCH NEXT写法上和SQL Server接近了SELECT * FROM employees ORDER BY emp_id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;但这个语法在Oracle里要求必须有ORDER BY否则直接报错。SQL Server的经典分页是TOP加子查询SELECT TOP 10 * FROM employees ORDER BY emp_id; -- 跨页需要配合 NOT IN 或 EXCEPT SELECT TOP 10 * FROM employees WHERE emp_id NOT IN ( SELECT TOP 20 emp_id FROM employees ORDER BY emp_id ) ORDER BY emp_id;SQL Server 2012之后终于支持了标准的OFFSET FETCHSELECT * FROM employees ORDER BY emp_id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;这里必须给OFFSET FETCH点个赞——它和Oracle 12c以后的写法几乎一样算是现代版本里最接近统一的方案。所以如果你写跨库工具能控制数据库版本的话尽量引导项目升级到支持OFFSET FETCH的版本代码可移植性会好很多。3.2 排序的隐藏差异排序规则与大小写排序看起来都一样但细节差异能坑到人。MySQL的排序规则由字符集和collation决定默认的utf8mb4_general_ci不区分大小写所以abc、ABC、Abc排在一起时会被当成同一个键值处理ORDER BY name的顺序完全取决于索引建立时的字节序很多人觉得“乱乱”的。如果想区分大小写得指定utf8mb4_bin排序规则或者用BINARY转换。Oracle的排序规则默认由NLS_SORT和NLS_COMP参数控制通常默认也是不区分大小写的但它支持语言化排序比如中文按拼音顺序排序需要设置NLS_SORTSCHINESE_PINYIN_M。写ORDER BY时如果期望的是拼音顺序实际输出却可能是按ASCII码或者笔画排的这种差异在报表场景里可大可小。SQL Server的排序规则由COLLATE子句直接控制比如Chinese_PRC_CI_AS表示中文、不区分大小写、区分重音。SQL Server的排序规则是可以在列级别覆盖的灵活性最高。跨库迁移时如果排序要求严格一致最省事的办法是都加上COLLATE或等价机制显式指定别指望默认值一致。3.3 字符串拼接与转数字的经典陷阱字符串拼接是日常需求里频率最高的操作三大库的写法正好三种风格。MySQL用CONCAT()函数可以传多个参数比如CONCAT(first_name, -, last_name)。MySQL的号是纯粹的算术加号不能用它拼字符串否则会做隐式数字转换拼出离谱的结果。Oracle用||符号这是SQL标准写法first_name || - || last_name。Oracle虽然也有CONCAT()但它只接受两个参数想要拼三个以上的字符串得嵌套调用特别麻烦所以大家都直接用||。SQL Server用号拼接字符串first_name - last_name。但它有个著名坑只要有一个操作数是NULL整个结果就是NULL。所以拼接前通常要写ISNULL(first_name, ) ISNULL(last_name, )。再说说热搜词里提到的“sqlserver 字符串转数字”这个坑也是经典之中的经典。SQL Server把一个字符串转数字最常见的是CAST和CONVERTSELECT CAST(123 AS INT); -- 123 SELECT CONVERT(INT, 123); -- 123 SELECT TRY_CAST(abc AS INT); -- NULL不报错MySQL转数字更直接写1230就能得到数字123CAST(123 AS UNSIGNED)也可以。Oracle转数字则是TO_NUMBER(123)它同样没有TRY机制字符串里带一个非数字字符就直接报ORA-01722: invalid number。所以跨库写转换逻辑时必须先想好“遇到非法数据时希望报错还是回退为NULL”再选对应函数这件事没人替你做。4. DML操作插、改、删的语法细节和坑4.1 INSERT多行插入与RETURNING三条SQL的基础INSERT INTO table (col1, col2) VALUES (v1, v2)都一样但批量插入就有明显差异。MySQL支持INSERT INTO后面直接跟多组VALUESINSERT INTO user (name, age) VALUES (张三, 20), (李四, 21), (王五, 22);这种写法在Oracle的老版本里不支持Oracle语法是INSERT ALL配合INTO多个子句或者用INSERT INTO ... SELECT ... UNION ALL的方式INSERT ALL INTO user (name, age) VALUES (张三, 20) INTO user (name, age) VALUES (李四, 21) INTO user (name, age) VALUES (王五, 22) SELECT 1 FROM DUAL;SQL Server默认支持多行VALUES写法和MySQL基本一致但批量插入的行数如果超过阈值可能影响日志空间需要注意。另一个易被忽略的点是RETURNING子句。Oracle插入后要拿回自增ID或某些计算值直接写INSERT INTO user (name, age) VALUES (张三, 20) RETURNING id INTO :out_id;这里:out_id是绑定变量在PL/SQL块里声明。SQL Server要用OUTPUT子句INSERT INTO user (name, age) OUTPUT INSERTED.id VALUES (张三, 20);MySQL也可以拿LAST_INSERT_ID()但局限性很大——它只返回当前连接最近一次插入的自增值如果一次插入多行只拿得到第一行ID想拿全部ID就得靠别的手段了。4.2 UPDATE与DELETE的多表关联写法这个差异几乎能把每个跨库开发的折磨一轮。MySQL支持在UPDATE里直接JOIN别的表UPDATE employees e JOIN departments d ON e.dept_id d.dept_id SET e.dept_name d.dept_name WHERE d.location 北京;SQL Server也支持类似的写法只是语法结构略有不同UPDATE e SET e.dept_name d.dept_name FROM employees e JOIN departments d ON e.dept_id d.dept_id WHERE d.location 北京;Oracle就比较轴了它不支持UPDATE...JOIN这种直观写法要么用相关子查询UPDATE employees e SET e.dept_name ( SELECT d.dept_name FROM departments d WHERE d.dept_id e.dept_id AND d.location 北京 ) WHERE EXISTS ( SELECT 1 FROM departments d WHERE d.dept_id e.dept_id AND d.location 北京 );要么用MERGE INTO来实现带关联的更新。Oracle每次遇到这种需求都让我觉得“杀鸡用牛刀”但没办法它的语法体系就是不允许多表直接UPDATE。DELETE的多表关联差异与此类似。MySQL支持DELETE FROM employees USING employees JOIN departments ...这种写法SQL Server支持DELETE FROM e FROM employees e JOIN departments d ...Oracle则必须先写子查询确定要删的ID列表再DELETE FROM employees WHERE dept_id IN (SELECT dept_id FROM departments WHERE location北京)。总之Oracle世界里“一步到位”的思维要收敛一下。4.3 MERGE与UPSERT的取舍很多业务场景需要“有则更新无则插入”三库的处理思路完全不同。Oracle的MERGE最成熟是官方推荐的写法MERGE INTO employees e USING (SELECT 1001 AS id, 张三 AS name FROM DUAL) s ON (e.id s.id) WHEN MATCHED THEN UPDATE SET e.name s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);SQL Server也有MERGE语法基本一致但有几个著名的坑。一是WHEN NOT MATCHED之外的AND条件如果写复杂了容易产生“多行匹配导致更新同一行”的运行时错误二是MERGE在事务隔离级别下有可能引发锁争议社区甚至有人建议SQL Server下用单独的UPDATE加INSERT替代MERGE以获得更好的可预测性。MySQL没有MERGE它的UPSERT写法和前两者完全不同用INSERT ... ON DUPLICATE KEY UPDATEINSERT INTO employees (id, name) VALUES (1001, 张三) ON DUPLICATE KEY UPDATE name 张三;注意这里触发更新逻辑的前提是唯一键或主键冲突。还有一个REPLACE INTO它本质上先删后插副作用是自增ID会变、外键关联可能会断非特殊情况不建议在生产里用REPLACE。一句话总结Oracle和SQL Server选MERGEMySQL选ON DUPLICATE KEY UPDATE迁移时这两类逻辑不能直接等价替换必须逐一评审。5. 函数与日期处理报账、报表和ETL最常用的战场5.1 字符串函数对照日常用到的字符串函数三库差异集中在一张表里操作MySQLOracleSQL Server字符串拼接CONCAT(a,b,c)a||b||cab字符串长度CHAR_LENGTH(s)LENGTH(s)LEN(s)字节长度LENGTH(s)LENGTHB(s)DATALENGTH(s)截取子串SUBSTRING(s,pos,len)SUBSTR(s,pos,len)SUBSTRING(s,pos,len)替换字符REPLACE(s,a,b)REPLACE(s,a,b)REPLACE(s,a,b)转大写/小写UPPER/LOWERUPPER/LOWERUPPER/LOWER去空格TRIM(s)TRIM(s)TRIM(s)聚合拼接GROUP_CONCATLISTAGGSTRING_AGG注意MySQL的GROUP_CONCAT和Oracle的LISTAGG在连接大量元素时有长度限制SQL Server的STRING_AGG从2017年才有之前只能用FOR XML PATH这种歪招拼接那一长串写法能背下来的人都是老僵尸级的了。还有一个容易忽略的细节Oracle的SUBSTR的起始位置支持负数从末尾开始数MySQL和SQL Server的SUBSTRING起始位置不支持负数MySQL的可以从0开始SQL Server有类似模拟写法写跨库代码时截取逻辑必须自己多套一层。5.2 日期函数对照日期处理是跨库迁移的深水区我每次做这种任务都先问一句“你们的日期逻辑集中在哪几个函数里”这里列最核心的几个。获取当前日期时间MySQLNOW()、CURDATE()、SYSDATE()OracleSYSDATE、CURRENT_TIMESTAMPSQL ServerGETDATE()、SYSDATETIME()格式化MySQLDATE_FORMAT(NOW(), %Y-%m-%d %H:%i:%s)OracleTO_CHAR(SYSDATE, YYYY-MM-DD HH24:MI:SS)SQL ServerCONVERT(VARCHAR(19), GETDATE(), 120)解析字符串MySQLSTR_TO_DATE(2024-01-15, %Y-%m-%d)OracleTO_DATE(2024-01-15, YYYY-MM-DD)SQL ServerCONVERT(DATE, 2024-01-15, 120)或CAST(2024-01-15 AS DATE)日期差MySQLDATEDIFF(end, start)直接返回天数Oracleend - start返回相差天数但两个DATE相减得到的是小数含时分秒换算SQL ServerDATEDIFF(DAY, start, end)必须先指定日期part日期加减MySQLDATE_ADD(date, INTERVAL 1 DAY)Oracledate 1直接加天数ADD_MONTHS(date, 1)加月份SQL ServerDATEADD(DAY, 1, date)这些差异如果靠脑子硬记很容易混最好是维护一张自己项目内部的函数映射表迁移时直接查表翻译比现场查文档快得多。5.3 类型转换与IF逻辑的差异类型转换上SQL Server的CAST和CONVERT双雄最直观CONVERT还能指定样式码比如日期格式的系列编号。Oracle的TO_CHAR、TO_NUMBER、TO_DATE三件套在字符串和日期之间来回横跳语义清晰但写起来啰嗦。MySQL的CAST基本够用CONVERT函数则可以选择字符集比如CONVERT(s USING utf8mb4)。IF逻辑的差异更值得说道MySQL有IF(条件, 真值, 假值)这个简洁函数很受大家喜爱Oracle没有IF()函数只能用CASE WHEN或DECODE()DECODE是Oracle的特色写法SELECT DECODE(status, A, 正常, B, 禁用, 未知) FROM orders;SQL Server也没有IF函数条件取值用CASE WHEN或IIF()从2012年开始支持。写跨库SQL时尽量把业务里所有IF()都改成标准CASE WHEN这样三库通用虽然啰嗦一点但换库的时候省心到偷笑。6. 存储过程、游标与事务控制6.1 存储过程的外观差异存储过程的热搜词很高必然有大量同学在这些语法细节上栽过跟头。MySQL存储过程的结构DELIMITER // CREATE PROCEDURE get_user(IN uid INT, OUT uname VARCHAR(50)) BEGIN SELECT name INTO uname FROM user WHERE id uid; END // DELIMITER ;Oracle用PL/SQL参数要区分IN/OUT/IN OUT变量定义放在IS后面赋值用:CREATE OR REPLACE PROCEDURE get_user(uid IN NUMBER, uname OUT VARCHAR2) IS BEGIN SELECT name INTO uname FROM user WHERE id uid; END get_user;PL/SQL里有个和T-SQL完全不同的点SELECT ... INTO必须有且只能有一行返回返回多行直接报TOO_MANY_ROWS返回零行报NO_DATA_FOUND处理起来要写异常块这点很多从MySQL转过来的人第一次写就懵了。SQL Server的存储过程CREATE PROCEDURE get_user uid INT, uname NVARCHAR(50) OUTPUT AS BEGIN SELECT uname name FROM user WHERE id uid; END变量用前缀表示赋值用SELECT 变量 列 FROM ...或SET 变量 值。还有一个经典差异T-SQL里如果不需要在存储过程里显示中间结果不要随意写SELECT因为驱动层ExecuteReader会把结果集当成返回给客户端的检索结果。6.2 游标和循环PL/SQL和T-SQL的经典差异写复杂的逐行处理逻辑时三库的循环语法也完全不同。MySQL里常用WHILE ... DO ... END WHILE配合游标DECLARE cur CURSOR FOR SELECT ...LOOP里通过FETCH ... INTO逐行取值然后用done标志位退出。Oracle的PL/SQL游标最简洁FOR rec IN (SELECT id FROM user) LOOP DBMS_OUTPUT.PUT_LINE(rec.id); END LOOP;它直接用隐式游标把整个SELECT循环处理干净了不需要显式声明变量和退出条件效率也高。SQL Server则要用CURSOR显式声明DECLARE id INT; DECLARE cur CURSOR FOR SELECT id FROM user; OPEN cur; FETCH NEXT FROM cur INTO id; WHILE FETCH_STATUS 0 BEGIN PRINT id; FETCH NEXT FROM cur INTO id; END CLOSE cur; DEALLOCATE cur;T-SQL的FETCH_STATUS 0判断退出条件是一个特色很多初学者忘了在循环体末尾再加一次FETCH NEXT结果死循环这是SQL Server游标最常见的bug。6.3 事务、异常处理和动态SQL事务控制基础语句三库都支持BEGIN TRANSACTION、COMMIT、ROLLBACK但细节有差异。MySQL默认每条SQL自动提交要手动事务必须显式写START TRANSACTION; ... COMMIT; -- 或 ROLLBACK;Oracle默认也不是自动提交隐式提交由DDL语句触发DML语句执行后如果不COMMIT会一直持有锁而且其他会话可能看到未提交的数据版本读一致性由Undo机制保证。这一点和我们平时在MySQL里的直觉完全不一样在Oracle里操作完数据不提交就关掉客户端数据直接没了但表锁可能被占住一段时间。SQL Server的TRANSACTION支持命名事务、保存点SAVE TRANSACTION配合TRY...CATCH做异常回滚是最顺手的BEGIN TRY BEGIN TRANSACTION; ... COMMIT; END TRY BEGIN CATCH ROLLBACK; END CATCH;Oracle的异常处理用EXCEPTION WHEN OTHERS THEN ROLLBACK;MySQL则用DECLARE EXIT HANDLER FOR SQLEXCEPTION或SIGNAL声明错误处理风格差异巨大。动态SQL也是重头戏。Oracle用EXECUTE IMMEDIATE加绑定变量SQL Server用EXEC sp_executesql sql, Np1 INT, p1 1MySQL用PREPARE加EXECUTE或存储过程中的CONCAT拼SQL。无论在哪一个库里我都要强调动态SQL一定要用绑定变量尽量不要直接拼接字符串防SQL注入只是一方面更重要的是Oracle和SQL Server绑定变量能有效复用执行计划性能差距在复杂语句里能到几十倍。7. 元数据与系统表三库各自的“户口本”7.1 MySQLinformation_schema的开放性MySQL的元数据查询最开放中央枢纽就是information_schema数据库。想查某张表的结构用DESC 表名或者查COLUMNS视图SELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA my_db AND TABLE_NAME user;MySQL还提供SHOW CREATE TABLE这种人性化命令一键导出建表语句做备份或迁移时最常用。7.2 OracleALL_TABLES与DBA视角Oracle的数据字典就厚重多了。普通用户能查的是USER_TABLES用户自己的表、ALL_TABLES有权限访问的所有表、DBA_TABLES全部表需要高权限。查字段信息用ALL_TAB_COLUMNSSELECT TABLE_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM ALL_TAB_COLUMNS WHERE OWNER SCOTT AND TABLE_NAME EMPLOYEES;新手最容易犯的错是在Oracle里执行SELECT * FROM sys.tables或者去看别的数据库的information_schema发现根本没有这个库——Oracle的元数据是数据字典不是数据库架构。7.3 SQL Serversys架构与系统视图SQL Server的元数据集中在sys架构下最常用的是sys.tables、sys.columns、sys.indexesSELECT t.name AS table_name, c.name AS column_name, ty.name AS data_type FROM sys.tables t JOIN sys.columns c ON t.object_id c.object_id JOIN sys.types ty ON c.user_type_id ty.user_type_id WHERE t.name User;它还提供了sp_help这种存储过程快速查看表结构和sp_who2查看阻塞进程是老DBA手边最顺手的工具。SQL Server还支持INFORMATION_SCHEMA.COLUMNS标准视图这一点和MySQL一致写跨库查询工具时优先用INFORMATION_SCHEMA标准视图会把兼容性拉高不少。8. 我踩过的跨库迁移坑与通用语法建议最后这部分不列什么高深原理就说我在真实项目里搬数据、改代码时撞过的一些南墙以及现在沉淀下来的操作习惯。第一个坑是“想当然的等价替换”。有一年我把一套用户积分模块从MySQL迁到Oracle本地一测发现所有分页查询全部报错我花了半天把所有LIMIT改成了FETCH FIRST。改完后另一批报表SQL又报错一看是用了IF(score 100, VIP, NORMAL)Oracle根本不认识IF函数只能改成CASE WHEN。这两批SQL看似都是“换个写法就行”但每一处都要仔细比对NULL语义、隐式转换、排序规则不是能手动批量替换的。第二个坑是日期类型和空字符串。从SQL Server往Oracle导数据时空字符串在Oracle里变成NULL业务逻辑判断全乱日期字段从SQL Server的DATETIME2搬到Oracle精度差异导致某些比较条件差了一毫秒对不上账。这类问题只能在迁移前做字段级映射分析没有捷径。第三个经验是写新功能时尽量让代码贴近标准SQL。具体做法是——字符串拼接优先用CONCAT的多参版本不要依赖某种库独有的拼法条件取值只用CASE WHEN别用IF和DECODE分页如果版本允许统一用OFFSET FETCHUUID主键比自增主键更容易在三库之间平移日期比较尽量用和而非BETWEEN避免精度边界问题。第四个经验是善用ORM或方言层。现在主流的ORM框架都支持多数据库方言配置MyBatis有databaseIdProviderHibernate有方言类EF Core有多数据库提供程序。如果项目本身就在多个数据库上跑老老实实引入方言层让框架去拼不同语法的SQL比自己维护一套SQL模板要省心得多。最后一个建议关乎心态不要试图背下所有差异这不现实也没必要。数据库语法差异不是死记硬背的知识点而是一套可以整理成映射表的工具。我在自己的工程文档里维护了一张速查表从数据类型、字符串函数、日期函数、分页语法到存储过程模板全都在一行行对照着写。这个表是每次迁移、每次新项目开工都翻出来对一遍的比临时看官方文档能省下大半天时间。如果你现在正被某个三库语法差异卡住建议按这个顺序排查先确认数据库版本和兼容模式再用最小案例分别在三库中执行对比最后把结论补进自己的映射表。这比你翻一百篇零散笔记都有用。
返回列表