mysql到Oracle迁移要注意的地方

从mysql迁移到oracle

项目开始阶段使用mysql数据库开发,后期改成oracle。数据抽象Db类对sql语句进行了封装,所以我主要工作就是参照mysql驱动的接口写一个oracle驱动。

基本的siud操作两者差别不是特别大,只有个别地方需要做一些工作:

  • 在oracle中建立相应的序列和触发器,实现mysql中的auto_increment字段
  • 使用子查询,实现mysql的limit以及sql_calc_found_rows功能
  • 对sql语句进行替换,避免mysql不出问题的字符或关键字在oracle中出问题

最后有两个问题比较致命:

  • 联表查询的时候会出现“列定义不明确”的错误
  • 分组查询的时候会出现“不是一个group查询”的错误

对于前者,原因就是a表中有字段xx,b表中也有字段xx,然后select a.*, b.*就会出现错误了。

对于后者,就是oracle中对group查询有这样的限制,select的字段、order中使用的字段,要么是出现在group by中,要么是被统计函数作用。而在mysql中却没有这样的限制。

这两个致命问题,我开始时是想要在oracle驱动上对sql语句进行hack,但是测试了一些例子发现没有什么规律而言,而且数据库驱动不维持表 的元信息,所以写起来比较困难,就算最后写出来,执行效率也不高。最后只能返回去改程序,同时也给自己长记性,要改改在mysql中养成的那些臭毛病。

此外还有一些需要注意的地方,比如:

  • 字段类型的转换
  • NULL的问题
  • 单双引号

 最后上代码:

  1 <?php
  2 define(‘_TS‘, microtime(true));
  3 error_reporting(E_ALL);
  4 header(‘Content-type: text/plain; charset=utf-8‘);
  5 
  6 $_db = ‘dbname‘;
  7 $_prefix = ‘pre_‘;
  8 $_new_prefix = ‘pre2_‘;
  9 
 10 $_sql_table = "select table_name, auto_increment, table_comment from information_schema.tables where table_schema = ‘".$_db."‘ and table_name like ‘".$_prefix."%‘;";
 11 $_sql_column = "select column_name, column_default, is_nullable, column_type, column_key, extra, column_comment from information_schema.columns where table_schema = ‘".$_db."‘ and table_name = ‘".$_prefix."%s‘ order by ordinal_position;";
 12 $_sql_checknull = "select count(*) from %s where %s = ‘‘ or %s is null";
 13 
 14 $db = mysql_connect(‘localhost‘, ‘root‘, ‘‘);
 15 mysql_select_db($_db);
 16 mysql_query(‘set names utf8;‘);
 17 
 18 function query($sql, $mode = MYSQL_ASSOC)
 19 {
 20     $q = mysql_query($sql);
 21     if (!$q)
 22         exit(‘query error: ‘. mysql_error());
 23 
 24     $ret = array();
 25     while ($tmp = mysql_fetch_array($q, $mode))
 26         $ret[] = $tmp;
 27 
 28     return $ret;
 29 }
 30 
 31 echo ‘SET DEFINE OFF;‘. PHP_EOL;
 32 
 33 foreach (query($_sql_table) as $table)
 34 {
 35     $t = substr($table[‘table_name‘], strlen($_prefix));
 36     if (!$t)
 37         exit("unknown table: {$t}");
 38 
 39     $new_table_name = $_new_prefix. $t;
 40 
 41     echo ‘-- ‘. $t. PHP_EOL;
 42     echo ‘DROP TABLE ‘. $new_table_name. ‘;‘. PHP_EOL;
 43     echo ‘CREATE TABLE ‘. $new_table_name. PHP_EOL;
 44     echo ‘(‘. PHP_EOL;
 45 
 46     $pks = array();
 47     $idx = array();
 48     $aic = ‘‘;
 49     $comments = ‘‘;
 50 
 51     $cArr = query(sprintf($_sql_column, $t));
 52     $i = count($cArr);
 53     foreach ($cArr as $c)
 54     {
 55         if ($c[‘extra‘] == ‘auto_increment‘)
 56             $aic = $c[‘column_name‘];
 57 
 58         echo $c[‘column_name‘]. ‘ ‘;
 59         if (!preg_match(‘/^(\w+)\b[^\d]*(\d+)?[^\d]*(\d+)?/‘, $c[‘column_type‘], $match))
 60             exit("cannot match: {$t}.{$c[‘column_name‘]}");
 61 
 62         array_shift($match);
 63         $type = strtolower(array_shift($match));
 64         switch ($type)
 65         {
 66         case ‘mediumint‘:
 67         case ‘int‘:
 68         case ‘tinyint‘:
 69             echo ‘NUMBER(‘. ($match[0] < 5 ? 5 : $match[0]). ‘)‘;
 70             break;
 71         case ‘decimal‘:
 72             echo ‘NUMBER(‘. $match[0]. ‘,‘. $match[1]. ‘)‘;
 73             break;
 74         case ‘char‘:
 75         case ‘varchar‘:
 76             $match[0] *= 2;
 77             echo ‘VARCHAR2(‘. ($match[0] > 4000 ? 4000 : $match[0]). ‘)‘;
 78             break;
 79         case ‘date‘:
 80         case ‘enum‘:
 81             echo ‘VARCHAR2(10)‘;
 82             break;
 83         case ‘text‘:
 84             echo ‘NCLOB‘;
 85             break;
 86         default:
 87             exit("unknown type: {$c[‘column_name‘]} {$type}");
 88         }
 89 
 90         if ($c[‘column_default‘] !== null)
 91             echo " DEFAULT ‘". $c[‘column_default‘]. "‘";
 92 
 93         switch (strtoupper($c[‘column_key‘]))
 94         {
 95         case ‘PRI‘:
 96             $pks[] = $c[‘column_name‘];
 97             break;
 98         case ‘MUL‘:
 99             $idx[$c[‘column_name‘]] = ‘INDEX‘;
100             break;
101         case ‘UNI‘:
102             $idx[$c[‘column_name‘]] = ‘UNIQUE INDEX‘;
103             break;
104         }
105 
106         if (strtoupper($c[‘is_nullable‘]) == ‘NO‘)
107         {
108             $checknull = query(sprintf($_sql_checknull, $table[‘table_name‘], $c[‘column_name‘], $c[‘column_name‘]), MYSQL_NUM);
109             if (!(int) $checknull[0][0])
110                 echo ‘ NOT NULL‘;
111         }
112 
113         if (--$i)
114             echo ‘,‘;
115 
116         echo PHP_EOL;
117 
118         $comments .= ‘COMMENT ON COLUMN ‘. $new_table_name. ‘.‘. $c[‘column_name‘];
119         $comments .= " IS ‘". $c[‘column_comment‘]. "‘;". PHP_EOL;
120     }
121 
122     if (count($pks))
123         echo ‘, PRIMARY KEY (‘.implode(‘, ‘, $pks).‘)‘. PHP_EOL;
124 
125     echo ‘);‘. PHP_EOL;
126 
127     foreach ($idx as $k => $v)
128         echo ‘CREATE ‘. $v. ‘ ‘. $new_table_name. ‘_‘. $k. ‘ ON ‘. $new_table_name. ‘ (‘. $k. ‘);‘. PHP_EOL;
129 
130     echo ‘COMMENT ON TABLE ‘. $new_table_name. " IS ‘". addslashes($table[‘table_comment‘]). "‘;". PHP_EOL;
131     echo $comments;
132 
133     foreach (query("select * from {$table[‘table_name‘]}") as $row)
134     {
135         echo ‘INSERT INTO ‘. $new_table_name. ‘ VALUES (‘;
136         foreach ($row as & $ri)
137             $ri = "‘".addslashes($ri)."‘";
138 
139         echo implode(‘,‘, $row);
140         echo ‘);‘. PHP_EOL;
141     }
142 
143     if ($aic)
144     {
145         echo ‘DROP SEQUENCE SEQU_‘. $t. ‘;‘. PHP_EOL;
146         echo ‘CREATE SEQUENCE SEQU_‘. $t. ‘ MINVALUE 1 MAXVALUE 999999999999 INCREMENT BY 1‘. PHP_EOL;
147         echo ‘START WITH ‘. $table[‘auto_increment‘]. ‘ NOCACHE ORDER NOCYCLE ;‘. PHP_EOL;
148         echo ‘CREATE OR REPLACE TRIGGER TRIG_‘. $t. ‘‘. PHP_EOL;
149         echo ‘BEFORE INSERT ON ‘. $new_table_name. ‘‘. PHP_EOL;
150         echo ‘FOR EACH ROW‘. PHP_EOL;
151         echo ‘BEGIN‘. PHP_EOL;
152         echo ‘  SELECT SEQU_‘. $t. ‘.NEXTVAL INTO :NEW.‘.$aic.‘ FROM DUAL;‘. PHP_EOL;
153         echo ‘END;‘. PHP_EOL;
154         echo ‘/‘. PHP_EOL;
155     }
156 
157     echo PHP_EOL;
158 }
159 
160 mysql_close($db);
161 echo ‘-- ‘;
162 printf(‘%.3f‘, microtime(true) - _TS);

 

<?php
// 比较关键的一些操作

// connect
$linkId = oci_new_connect(‘user‘, ‘pass‘, ‘host/sid‘, ‘utf8‘);

// query
$stmt = oci_parse($linkId, $sql);
$mode = false ? OCI_DEFAULT : OCI_COMMIT_ON_SUCCESS;
if (!oci_execute($stmt, $mode))
    throw new Exception(‘query error: ‘. $sql);

// fetch
$ret = array();
while ($tmp = oci_fetch_array($stmt, OCI_BOTH + OCI_RETURN_NULLS + OCI_RETURN_LOBS))
    $ret[] = array_change_key_case($tmp);

// select
$sql = ‘select %s ‘;
$sql .= ‘from %s ‘;
$sql .= ‘left join %s on %s ‘;
$sql .= ‘where %s ‘;
$sql .= ‘group by %s ‘;
$sql .= ‘having %s ‘;
$sql .= ‘order by %s ‘;

$sqlCalcFoundRows = ‘select max(rownum) from (‘. $sql. ‘)‘;
$sql = ‘select * from (select a.*, rownum r from (‘. $sql. ‘) a where rownum <= 10) b where r > 0‘;

// insert
$sql = ‘insert into %s (%s) values (%s)‘;
$sqlInsertId = ‘select sequ_%s.currval from %s‘;

// update
$sql = ‘update %s set %s ‘;

// delete
$sql = ‘delete from %s %s‘;

// replace
$sql = ‘merge into %s a using (select %s from dual where rownum < 2) b on (a.%s = b.%s) when matched then update set %s when not matched then insert (%s) values (%s)‘;

// startTransaction

// commitTransaction
oci_commit($linkId);

// rollbackTransaction
oci_rollback($linkId);


1. 自动增长的数据类型处理

MYSQL有自动增长的数据类型,插入记录时不用操作此字段,会自动获得数据值。ORACLE没有自动增长的数据类型,需要建立一个自动增长的序列号,插入记录时要把序列号的下一个值赋于此字段。

CREATE SEQUENCE 序列号的名称 (最好是表名+序列号标记) INCREMENT BY 1 START WITH 1 MAXVALUE 99999 CYCLE NOCACHE;

INSERT 语句插入这个字段值为: 序列号的名称.NEXTVAL

2. 单引号的处理

MYSQL里可以用双引号包起字符串,ORACLE里只可以用单引号包起字符串。在插入和修改字符串前必须做单引号的替换:把所有出现的一个单引号替换成两个单引号。当然你如果使用 Convert Mysql to Oracle 工具就不用考虑这个问题

3.长字符串的处理

在ORACLE中,INSERT和UPDATE时最大可操作的字符串长度小于等于4000个单字节, 如果要插入更长的字符串, 请考虑字段用CLOB类型,方法借用ORACLE里自带的DBMS_LOB程序包。插入修改记录前一定要做进行非空和长度判断,不能为空的字段值和超出长 度字段值都应该提出警告,返回上次操作。

4. 翻页的SQL语句的处理

MYSQL处理翻页的SQL语句比较简单,用LIMIT 开始位置, 记录个数。ORACLE处理翻页的SQL语句就比较繁琐了。每个结果集只有一个ROWNUM字段标明它的位置, 并且只能用ROWNUM<100, 不能用ROWNUM>80。

以下是经过分析后较好的两种ORACLE翻页SQL语句( ID是唯一关键字的字段名 ):

语句一:

SELECT ID, [FIELD_NAME,...] FROM TABLE_NAME WHERE ID IN ( SELECT ID FROM (SELECT ROWNUM AS NUMROW, ID FROM TABLE_NAME WHERE 条件1 ORDER BY 条件2) WHERE NUMROW > 80 AND NUMROW < 100 ) ORDER BY 条件3;

语句二:

SELECT * FROM (( SELECT ROWNUM AS NUMROW, c.* from (select [FIELD_NAME,...] FROM TABLE_NAME WHERE 条件1 ORDER BY 条件2) c) WHERE NUMROW > 80 AND NUMROW < 100 ) ORDER BY 条件3;

5. 日期字段的处理

MYSQL日期字段分DATE和TIME两种,ORACLE日期字段只有DATE,包含年月日时分秒信息,用当前数据库系统时间为SYSDATE, 精确到秒。

日期字段的数学运算公式有很大的不同。MYSQL找到离当前时间7天用 DATE_FIELD_NAME > SUBDATE(NOW(),INTERVAL 7 DAY)ORACLE找到离当前时间7天用 DATE_FIELD_NAME >SYSDATE - 7;

6. 字符串的模糊比较

MYSQL里用 字段名 like ‘%字符串%‘,ORACLE里也可以用 字段名 like ‘%字符串%‘ 但这种方法不能使用索引, 速度不快,用字符串比较函数 instr(字段名,‘字符串‘)>0 会得到更精确的查找结果。

7. 空字符的处理

MYSQL的非空字段也有空的内容,ORACLE里定义了非空字段就不容许有空的内容。按MYSQL的NOT NULL来定义ORACLE表结构, 导数据的时候会产生错误。因此导数据时要对空字符进行判断,如果为NULL或空字符,需要把它改成一个空格的字符串。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。